Skip to content

<feature>[dependencies]: Upgrade java version to 17#3280

Open
zstack-robot-2 wants to merge 25 commits into5.5.6from
sync/ye.zou/java17@@2
Open

<feature>[dependencies]: Upgrade java version to 17#3280
zstack-robot-2 wants to merge 25 commits into5.5.6from
sync/ye.zou/java17@@2

Conversation

@zstack-robot-2
Copy link
Collaborator

ZQLImpact

Change-Id: I656f6963726b77727363656d756465766b6a7a6a

sync from gitlab !9100

@coderabbitai
Copy link

coderabbitai bot commented Feb 2, 2026

Walkthrough

本次提交引入 Java 17 运行时与模块开放配置、多个构建与验证脚本、广泛将弃用的反射调用 Class.newInstance() 替换为 getConstructor().newInstance(),并包含架构与构建加速文档、若干功能性小改(并发同步、敏感配置脱敏、IP 过滤、容量释放策略等)。

Changes

Cohort / File(s) Summary
JVM 与 Java17 配置
.mvn/jvm.config, conf/jvm-options-java17.conf
新增/修改 JVM 启动选项与 Java 17 模块开放、导出规则,以及内存/GC 配置。
构建与验证脚本
build/verify-java17.sh, build/fix-groovy-compiler.sh, build/debug.sh, build/zstack, build/zstack-debug, build/...
新增 Java17 验证脚本、Groovy 编译器修复脚本;运行/调试脚本可加载 conf/jvm-options-java17.conf(注意部分脚本变量拼写小差异)。
反射 API 现代化(核心、多模块)
compute/.../HostAllocatorChainBuilder.java, compute/.../VmInstanceBase.java, configuration/.../ConfigurationManagerImpl.java, core/.../CloudBusImpl2.java, core/.../DatabaseFacadeImpl.java, core/.../GarbageCollectorManagerImpl.java, core/.../RESTApiFacadeImpl.java, core/.../FlowChainBuilder.java, ...
将大量 Class.newInstance() 替换为 Class.getConstructor().newInstance(),并在若处扩展了异常声明/捕获(NoSuchMethodException/InvocationTargetException)。
反射 API 现代化(其余模块与测试)
header/.../CheckFlow.java, identity/.../AccountManagerImpl.java, longjob/.../*.java, plugin/sugonSdnController/.../ApiConnector*.java, rest/.../RestServer.java, search/..., storage/.../AbstractUsageReport.java, tag/.../PatternedSystemTag.java, utils/.../*.java, test/...
同上,跨插件、搜索、存储、标签、工具与测试框架更新反射实例化方式并调整相关异常处理。
并发与同步改动(管理节点/CloudBus)
core/src/main/java/org/zstack/core/cloudbus/ResourceDestinationMakerImpl.java, portal/src/main/java/org/zstack/portal/managementnode/ManagementNodeManagerImpl.java
为多方法添加同步(synchronized)或生命周期锁,改进节点加入/离开与 hash-ring 与数据库状态的并发协调,引入两轮确认机制以减少误移除。
敏感信息与配置处理
header/src/main/java/.../ExternalPrimaryStorageInventory.java, conf/jvm-options-java17.conf
对外部主存储 config 进行脱敏处理;新增 Java17 模块开放/导出规则文件含注释(部分中文注释)。
资源与行为调整
network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java, plugin/ceph/.../CephPrimaryStorageBase.java, plugin/zbs/.../ZbsStorageController.java, plugin/loadBalancer/.../LoadBalancerApiInterceptor.java
过滤返回的可用 IP 以排除保留 IP 段;删除 Ceph 快照容量释放改为比例释放;HTTP 调用新增尝试下一个 MDS 的重试分支;添加负载均衡删除消息的拦截校验。
Vm 与 NIC 健壮性改动
compute/src/main/java/org/zstack/compute/vm/VmNicManagerImpl.java
为 NIC 查找增加空检查并在找不到时提前返回,避免 NPE。
API/消息与状态机微调
header/src/main/java/org/zstack/header/vm/VmInstanceState.java, 若干 REST/CloudBus/长作业类
在 Destroying 状态添加由 stopped 的过渡;消息/事件反射实例化方式更新;部分类增加 InvocationTargetException 导入与捕获。
文档与构建指南
docs/ARCHITECTURE.md, docs/BUILD-ACCELERATION.md
新增架构参考与构建加速指南文档(大量新增文档内容)。
格式化与小修
header/src/main/groovy/..., search/src/main/groovy/...Doc_zh_cn.groovy, header/.../OwnedByAccountAspect.aj, image/.../ImageManagerImpl.java, utils/.../test/TestNetworkUtils.java, runMavenProfile
若干文件的格式化、删除未使用导入、尾随换行以及在 premium profile 中添加 -T 1C 并行构建标志。

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Java 十七风拂堤,反射更新步轻盈,
构建脚本唱新曲,文档铺就长途程,
节点守护两轮审,密钥藏于清单中,
小兔欢跳庆一声,代码更健朗生机盈! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR标题遵循指定的格式 [scope]: ,长度为51个字符,在72字符限制内。标题准确描述了主要更改内容:将Java版本升级到17。
Description check ✅ Passed PR描述虽然简洁,但与变更集相关。描述提及"ZQLImpact"和"sync from gitlab !9100",与Java 17升级的目标相关。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch sync/ye.zou/java17@@2

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.40.5)
compute/src/main/java/org/zstack/compute/vm/VmInstanceBase.java
plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageBase.java
plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerApiInterceptor.java

[]

  • 1 others

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🤖 Fix all issues with AI agents
In `@build/debug.sh`:
- Around line 11-16: Replace the Chinese comment "加载Java 17 JVM参数" with an
English comment describing the same purpose (e.g., "Load Java 17 JVM options")
so comments are consistently in English; update the comment near the
jvm_options_file and jvm_options declarations to use the corrected English text.
- Line 18: 第 18 行条件判断使用了非 POSIX 的比较运算符 "=="(在 if [ x"$is_suspend" == x"true" ];
then),会在严格的 sh 实现中失败;将该比较改为单等号 "="(即使用 if [ x"$is_suspend" = x"true" ];
then)以确保与 POSIX sh(dash 等)兼容并提高可移植性,保留变量前的 x 前缀和引号以防空值或特殊字符。

In `@build/fix-groovy-compiler.sh`:
- Around line 3-15: The script contains Chinese comments and echo output (lines
around the header and description) that must be converted to English to match
repo conventions; update the top comment block and all echo strings (e.g., the
header lines currently printed with echo
"==========================================" and the "批量修复Groovy编译配置" message)
to clear, correctly spelled English, and ensure inline comments (above set -e,
SCRIPT_DIR, PROJECT_ROOT) are rewritten in English as well so all user-facing
and inline text in this script (fix-groovy-compiler.sh) uses English only.
- Around line 95-266: The script contains Chinese text and uses an unquoted
heredoc for the embedded Python block; replace all Chinese comments and
user-facing messages with English and switch the heredoc to a single-quoted form
(use <<'PYTHON_SCRIPT') so the shell will not expand variables inside the block,
and pass the FILE path into the Python script via an exported environment
variable (or as an argument) instead of relying on unescaped expansions; update
prints like "✅ 修复完成"/"⚠️  未找到需要替换的内容" and shell echoes "✅ 修复成功"/"⚠️ 
修复失败或无需修复,请手动检查" to clear English equivalents, ensure the Python logic
(functions/variables: pattern, pattern2, replace_plugin, lines/new_lines,
version detection using version_line and version_match) continues to work with
the quoted heredoc and the existing escaped ${project.java.version} strings, and
verify the script exits with the same status behavior.

In `@build/verify-java17.sh`:
- Around line 65-76: The current if-check uses a pipeline (mvn test ... | tee
/tmp/java17-test.log) so tee hides mvn's exit code; enable proper failure
detection by either enabling pipefail (set -o pipefail before the pipeline) or
capture mvn's exit with PIPESTATUS after the pipeline (e.g., inspect
${PIPESTATUS[0]}), then use that exit code to decide the if/else branch for the
mvn invocation shown in the mvn test -pl utils,abstraction ... 2>&1 | tee
/tmp/java17-test.log command and subsequent TEST_FAILED logic.
- Around line 1-98: The script contains Chinese comments and output strings;
replace all Chinese text with clear English equivalents (e.g., change header
echoes, section markers "[1/6]".."[6/6]", comment lines, error messages and
suggestions) while preserving logic and variable names like JAVA_VERSION,
SPRING_VERSION, HIBERNATE_VERSION, ASPECTJ_VERSION, GROOVY_VERSION and temp log
/tmp/java17-test.log; update messages for success/failure (e.g., "❌ 错误: 需要Java
17..." → "ERROR: Java 17 is required, current version: ...", "✓ 核心模块编译成功" → "OK:
core modules compiled") and translate all other echoed guidance and comments to
idiomatic, correctly spelled English.

In `@build/zstack`:
- Line 23: Replace the Chinese comment "# 加载Java 17 JVM参数" with an English
equivalent (e.g., "# Load Java 17 JVM parameters" or "# Load Java 17 JVM
options") in build/zstack; ensure the updated comment text for the JVM parameter
section uses correct English and scan the surrounding comments in the same file
for any other Chinese text to convert to English as well.

In `@build/zstack-debug`:
- Line 23: The comment at line with "# 加载Java 17 JVM参数" must be converted to
English to follow the project's language guidelines; replace the Chinese comment
with an appropriate English equivalent such as "Load Java 17 JVM parameters" or
"Load Java 17 JVM options" in the build/zstack-debug file so all comments and
messages are in correct English and consistent across the codebase.
- Around line 29-30: The script defines the JVM options into a misspelled
variable javaOptitons but then invokes java using javaOptions, so the JVM flags
are never passed; fix by correcting the variable name so both places use the
same identifier (either rename javaOptitons to javaOptions where it’s defined or
change the invocation to use javaOptitons) and ensure the value includes the
intended flags (-Xdebug -Xms256m -Xmx512m -XX:+HeapDumpOnOutOfMemoryError
-Xrunjdwp:transport=dt_socket,address=8787,server=y,suspend=y) so the JVM
receives the parameters.

In `@conf/jvm-options-java17.conf`:
- Around line 1-18: Replace all Chinese comments in this JVM options file with
clear English comments: change the file header comment to "Java 17 module system
configuration", the reflective-access note "开放反射访问(226处setAccessible需要)" to
"Open reflective access (required for setAccessible usages)", the internal-API
export note "导出内部API(sun.misc.Signal需要)" to "Export internal APIs (required for
sun.misc.Signal)", the Hibernate note "Hibernate需要" to "Required by Hibernate",
and the AspectJ note "AspectJ需要" to "Required by AspectJ"; keep the option lines
(--add-opens, --add-exports, --add-opens java.base/java.time, --add-opens
java.base/java.lang.invoke) unchanged.

In `@docs/ARCHITECTURE.md`:
- Around line 1-1199: The document "ZStack 后端架构索引" is in Chinese but the repo
requires English-only content; update this ARCHITECTURE.md by providing an
English version (either replace the Chinese text or add a full English
translation alongside the Chinese), ensuring all headings, descriptions, table
entries, code comments/examples and appended notes (e.g., "文档版本", "最后更新") are
converted to proper English, and keep structural identifiers (like
class/interface names: CloudBus, DatabaseFacade, ThreadFacade, FlowChain,
GlobalConfig, etc.) unchanged so readers can locate code references.

In `@docs/BUILD-ACCELERATION.md`:
- Around line 1-141: The document "ZStack 构建加速指南" is written in Chinese but the
repo requires English-only documentation; please convert this file to English
(or add a complete English version alongside the Chinese) so all headings (e.g.,
"ZStack 构建加速指南"), section titles, prose, and any inline comments are in correct
English and free of spelling errors; keep command examples and flags unchanged,
preserve technical details (mvnd/mvn usage, profiles, flags like -P premium,
-Dmevoco.skip.obfuscate=true, .mvn/jvm.config settings, and plugin notes such as
gmavenplus fork) and, if you retain the Chinese version, add a clear bilingual
header indicating both languages and ensure filenames/links referenced in the
text remain consistent.

In `@longjob/src/main/java/org/zstack/longjob/LongJobFactoryImpl.java`:
- Around line 54-57: The catch block in LongJobFactoryImpl that handles
InstantiationException | IllegalAccessException | NoSuchMethodException |
InvocationTargetException currently calls e.printStackTrace(); replace this with
a proper logger call (e.g., logger.error("Failed to instantiate LongJob via
reflection for " + it.getName(), e)) so the exception is recorded via the class
logger; update the catch to log the exception and any contextual info (the
constructor/type or variable job) instead of printing the stack trace.
🧹 Nitpick comments (2)
runMavenProfile (1)

502-502: 建议验证并行构建的兼容性。

添加 -T 1C 启用 Maven 并行构建(每 CPU 核心 1 个线程)可以显著加快构建速度,但需要确保:

  1. 所有模块间依赖已正确声明
  2. 使用的 Maven 插件是线程安全的
  3. 没有资源竞争问题(如文件锁、端口占用等)

建议在合并前确认并行构建在 CI 环境中测试通过。

tag/src/main/java/org/zstack/tag/PatternedSystemTag.java (1)

126-136: 反射更新正确,但可考虑使用更精确的异常类型。

将 catch 从特定异常类型扩展为通用 Exception 会降低异常处理的精确性。建议保持与其他文件一致的异常处理模式。

♻️ 建议的优化方案
     public String hideSensitiveInfo(String tag) {
         Class<? extends SensitiveTagOutputHandler> clz = this.annotation.customizeOutput();
         String result = tag;
         try {
             SensitiveTagOutputHandler sensitiveOutputHandler = clz.getConstructor().newInstance();
             result = sensitiveOutputHandler.desensitizeTag(this, tag);
-        } catch (Exception e) {
+        } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | java.lang.reflect.InvocationTargetException e) {
             logger.warn("exception happened :", e);
         }
         return result;
     }

Comment on lines +11 to +16
# 加载Java 17 JVM参数
jvm_options_file="$conf_dir/jvm-options-java17.conf"
jvm_options=""
if [ -f "$jvm_options_file" ]; then
jvm_options=$(cat "$jvm_options_file" | grep -v '^#' | tr '\n' ' ')
fi
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Line 11 的中文注释请改为英文

注释语言需统一为英文。

As per coding guidelines: 代码里不应当有有中文,包括报错、注释等都应当使用正确的、无拼写错误的英文来写

🤖 Prompt for AI Agents
In `@build/debug.sh` around lines 11 - 16, Replace the Chinese comment "加载Java 17
JVM参数" with an English comment describing the same purpose (e.g., "Load Java 17
JVM options") so comments are consistently in English; update the comment near
the jvm_options_file and jvm_options declarations to use the corrected English
text.

jvm_options=$(cat "$jvm_options_file" | grep -v '^#' | tr '\n' ' ')
fi

if [ x"$is_suspend" == x"true" ]; then
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n build/debug.sh | sed -n '10,25p'

Repository: MatheMatrix/zstack

Length of output: 742


🏁 Script executed:

# 检查是否存在 build/debug.sh 文件
ls -la build/debug.sh 2>&1 | head -20

Repository: MatheMatrix/zstack

Length of output: 125


🌐 Web query:

POSIX sh test operator == vs = compatibility dash busybox

💡 Result:

  • POSIX sh / POSIX test ([ ... ]) string equality operator is = (and != for inequality). == is not required by POSIX. Use: if [ "$a" = "$b" ]; then ...; fi.[1]
  • dash (Debian Almquist shell) is strict here: [ x == x ] fails with [: unexpected operator, so == is not compatible with dash scripts.[2]
  • BusyBox ash/sh often accepts == in [ ... ] as an extension, so [ x == x ] may work there—but that’s exactly why it’s a portability trap: it works on BusyBox but breaks on dash.[2]
  • Some test implementations (e.g., GNU coreutils test) document == as a non-portable synonym for =.[3]
  • [[ ... ]] is not POSIX sh (and its behavior varies); BusyBox only supports it in some builds and with limited compatibility options, so don’t use it in scripts that must run under dash/POSIX sh.[4]

Rule for maximum compatibility (dash + BusyBox + POSIX): use = inside [ ... ], not ==.[1][2]

Sources:
[1] man7 test(1) (string tests: STRING1 = STRING2) (man7.org)
[2] Unix & Linux SE showing dash rejects == while BusyBox accepts it (unix.stackexchange.com)
[3] GNU coreutils manual: == is a synonym for = but “not as portable” (gnu.org)
[4] Unix & Linux SE on BusyBox [[...]] support being optional/limited (unix.stackexchange.com)

Citations:


在 POSIX sh 中应使用单等号 = 而非 ==

第 18 行使用了 == 作为条件比较操作符,但 POSIX 标准 sh 规定应使用单等号 =。在 dash(Debian Almquist shell)等严格遵循 POSIX 标准的 shell 实现中,== 会导致错误 [: unexpected operator。此外,== 是一些 shell 的非标准扩展,使用它会降低脚本的可移植性。

🔧 建议修复
-if [ x"$is_suspend" == x"true" ]; then
+if [ x"$is_suspend" = x"true" ]; then
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if [ x"$is_suspend" == x"true" ]; then
if [ x"$is_suspend" = x"true" ]; then
🧰 Tools
🪛 Shellcheck (0.11.0)

[warning] 18-18: In POSIX sh, == in place of = is undefined.

(SC3014)

🤖 Prompt for AI Agents
In `@build/debug.sh` at line 18, 第 18 行条件判断使用了非 POSIX 的比较运算符 "=="(在 if [
x"$is_suspend" == x"true" ]; then),会在严格的 sh 实现中失败;将该比较改为单等号 "="(即使用 if [
x"$is_suspend" = x"true" ]; then)以确保与 POSIX sh(dash 等)兼容并提高可移植性,保留变量前的 x
前缀和引号以防空值或特殊字符。

Comment on lines +3 to +15
# 批量修复Groovy编译配置脚本
# 用于将groovy-eclipse-compiler替换为标准Java编译器或GMavenPlus

set -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"

cd "$PROJECT_ROOT"

echo "=========================================="
echo "批量修复Groovy编译配置"
echo "=========================================="
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

脚本注释与输出请改为英文

Line 3-15 等处包含中文注释/输出,与当前仓库规范不一致,请统一为英文。

As per coding guidelines: 代码里不应当有有中文,包括报错、注释等都应当使用正确的、无拼写错误的英文来写

🤖 Prompt for AI Agents
In `@build/fix-groovy-compiler.sh` around lines 3 - 15, The script contains
Chinese comments and echo output (lines around the header and description) that
must be converted to English to match repo conventions; update the top comment
block and all echo strings (e.g., the header lines currently printed with echo
"==========================================" and the "批量修复Groovy编译配置" message)
to clear, correctly spelled English, and ensure inline comments (above set -e,
SCRIPT_DIR, PROJECT_ROOT) are rewritten in English as well so all user-facing
and inline text in this script (fix-groovy-compiler.sh) uses English only.

Comment on lines +95 to +266
# 使用Python进行精确替换(因为sed处理多行和缩进比较复杂)
python3 <<PYTHON_SCRIPT
import re
import sys

file_path = "$FILE"

with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()

# 匹配groovy-eclipse-compiler配置块
# 匹配从<plugin>开始到</plugin>结束,包含groovy-eclipse-compiler的整个块
pattern = r'''\s*<plugin>\s*
\s*<groupId>org\.apache\.maven\.plugins</groupId>\s*
\s*<artifactId>maven-compiler-plugin</artifactId>\s*
\s*<version>.*?</version>\s*
\s*<configuration>\s*
\s*<compilerId>groovy-eclipse-compiler</compilerId>\s*
\s*<source>\$\{project\.java\.version\}</source>\s*
\s*<target>\$\{project\.java\.version\}</target>\s*
\s*<debuglevel>lines,vars,source</debuglevel>\s*
\s*<debug>true</debug>\s*
\s*</configuration>\s*
\s*<dependencies>\s*
\s*<dependency>\s*
\s*<groupId>org\.codehaus\.groovy</groupId>\s*
\s*<artifactId>groovy-eclipse-compiler</artifactId>\s*
\s*<version>.*?</version>\s*
\s*</dependency>\s*
\s*<dependency>\s*
\s*<groupId>org\.codehaus\.groovy</groupId>\s*
\s*<artifactId>groovy-eclipse-batch</artifactId>\s*
\s*<version>.*?</version>\s*
\s*</dependency>\s*
\s*</dependencies>\s*
\s*</plugin>'''

# 更灵活的匹配模式(处理不同的缩进和换行)
pattern2 = r'''(\s*)<plugin>\s*
\1<groupId>org\.apache\.maven\.plugins</groupId>\s*
\1<artifactId>maven-compiler-plugin</artifactId>\s*
\1<version>([^<]+)</version>\s*
\1<configuration>\s*
\1\s*<compilerId>groovy-eclipse-compiler</compilerId>\s*
\1\s*<source>\$\{project\.java\.version\}</source>\s*
\1\s*<target>\$\{project\.java\.version\}</target>\s*
\1\s*<debuglevel>lines,vars,source</debuglevel>\s*
\1\s*<debug>true</debug>\s*
\1</configuration>\s*
\1<dependencies>\s*
\1\s*<dependency>\s*
\1\s*<groupId>org\.codehaus\.groovy</groupId>\s*
\1\s*<artifactId>groovy-eclipse-compiler</artifactId>\s*
\1\s*<version>[^<]+</version>\s*
\1\s*</dependency>\s*
\1\s*<dependency>\s*
\1\s*<groupId>org\.codehaus\.groovy</groupId>\s*
\1\s*<artifactId>groovy-eclipse-batch</artifactId>\s*
\1\s*<version>[^<]+</version>\s*
\1\s*</dependency>\s*
\1</dependencies>\s*
\1</plugin>'''

# 尝试匹配并替换
def replace_plugin(match):
indent = match.group(1)
version = match.group(2).strip()
replacement = f'''{indent}<plugin>
{indent} <groupId>org.apache.maven.plugins</groupId>
{indent} <artifactId>maven-compiler-plugin</artifactId>
{indent} <version>{version}</version>
{indent} <configuration>
{indent} <source>${{project.java.version}}</source>
{indent} <target>${{project.java.version}}</target>
{indent} <debug>true</debug>
{indent} <parameters>true</parameters>
{indent} </configuration>
{indent}</plugin>'''
return replacement

# 使用更简单的方法:逐行处理
lines = content.split('\n')
new_lines = []
i = 0
in_plugin = False
in_config = False
in_dependencies = False
plugin_start = -1
indent = ""

while i < len(lines):
line = lines[i]

# 检测plugin开始
if '<plugin>' in line and 'maven-compiler-plugin' in '\n'.join(lines[max(0, i-3):i+3]):
# 向前查找groupId和artifactId
if i > 0 and 'maven-compiler-plugin' in lines[i-1]:
plugin_start = len(new_lines)
indent = line[:len(line) - len(line.lstrip())]
in_plugin = True
new_lines.append(line)
i += 1
continue

# 如果在plugin块中,检测groovy-eclipse-compiler
if in_plugin:
if '<compilerId>groovy-eclipse-compiler</compilerId>' in line:
# 找到了,需要替换整个plugin块
# 回退到plugin开始位置
new_lines = new_lines[:plugin_start]
# 添加新的标准配置
version_line = None
for j in range(plugin_start, i):
if '<version>' in lines[j] and '</version>' in lines[j]:
version_line = lines[j]
break

version = '${project.compiler.version}'
if version_line:
version_match = re.search(r'<version>([^<]+)</version>', version_line)
if version_match:
version = version_match.group(1).strip()

new_lines.append(f'{indent}<plugin>')
new_lines.append(f'{indent} <groupId>org.apache.maven.plugins</groupId>')
new_lines.append(f'{indent} <artifactId>maven-compiler-plugin</artifactId>')
new_lines.append(f'{indent} <version>{version}</version>')
new_lines.append(f'{indent} <configuration>')
new_lines.append(f'{indent} <source>${{project.java.version}}</source>')
new_lines.append(f'{indent} <target>${{project.java.version}}</target>')
new_lines.append(f'{indent} <debug>true</debug>')
new_lines.append(f'{indent} <parameters>true</parameters>')
new_lines.append(f'{indent} </configuration>')
new_lines.append(f'{indent}</plugin>')

# 跳过到</plugin>
while i < len(lines) and '</plugin>' not in lines[i]:
i += 1
if i < len(lines):
i += 1 # 跳过</plugin>
in_plugin = False
continue
elif '</plugin>' in line:
in_plugin = False
new_lines.append(line)
i += 1
continue

new_lines.append(line)
i += 1

new_content = '\n'.join(new_lines)

# 如果内容有变化,写入文件
if new_content != content:
with open(file_path, 'w', encoding='utf-8') as f:
f.write(new_content)
print(" ✅ 修复完成")
sys.exit(0)
else:
print(" ⚠️ 未找到需要替换的内容(可能已修复或格式不同)")
sys.exit(1)

PYTHON_SCRIPT

if [ $? -eq 0 ]; then
FIXED=$((FIXED + 1))
echo " ✅ 修复成功"
else
echo " ⚠️ 修复失败或无需修复,请手动检查"
SKIPPED=$((SKIPPED + 1))
fi
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n build/fix-groovy-compiler.sh | head -100 | tail -20

Repository: MatheMatrix/zstack

Length of output: 592


🏁 Script executed:

wc -l build/fix-groovy-compiler.sh

Repository: MatheMatrix/zstack

Length of output: 94


🏁 Script executed:

sed -n '90,110p' build/fix-groovy-compiler.sh

Repository: MatheMatrix/zstack

Length of output: 635


🏁 Script executed:

rg '使用|修复|错误|检测|匹配' build/fix-groovy-compiler.sh -c

Repository: MatheMatrix/zstack

Length of output: 64


🏁 Script executed:

sed -n '1,30p' build/fix-groovy-compiler.sh

Repository: MatheMatrix/zstack

Length of output: 862


🏁 Script executed:

sed -n '200,266p' build/fix-groovy-compiler.sh

Repository: MatheMatrix/zstack

Length of output: 2478


🏁 Script executed:

sed -n '95,200p' build/fix-groovy-compiler.sh

Repository: MatheMatrix/zstack

Length of output: 3346


消除代码中的中文内容并改进 heredoc 处理方式

此脚本违反编码规范:代码、注释和错误消息中包含大量中文(第 2、3、12、15-17、85、95、263、265、267 行等),应使用正确的英文改写。

其次,第 96 行的未引号 heredoc 允许 shell 展开变量,虽然当前代码中的 ${project.java.version} 已通过 \$ 转义避免了问题,但使用带引号的 heredoc(<<'PYTHON_SCRIPT')并通过环境变量传递 $FILE 是更安全、更清晰的做法:

🔧 建议修复
-    # 使用Python进行精确替换(因为sed处理多行和缩进比较复杂)
-    python3 <<PYTHON_SCRIPT
+    # Use Python for precise replacement (sed is complex with multiline and indentation)
+    if FILE="$FILE" python3 <<'PYTHON_SCRIPT'
 import re
 import sys
+import os
@@
-file_path = "$FILE"
+file_path = os.environ["FILE"]
@@
-PYTHON_SCRIPT
-
-    if [ $? -eq 0 ]; then
-        FIXED=$((FIXED + 1))
-        echo "  ✅ 修复成功"
-    else
-        echo "  ⚠️  修复失败或无需修复,请手动检查"
-        SKIPPED=$((SKIPPED + 1))
-    fi
+PYTHON_SCRIPT
+    then
+        FIXED=$((FIXED + 1))
+        echo "  ✅ Fix successful"
+    else
+        echo "  ⚠️  Fix failed or no changes needed, please check manually"
+        SKIPPED=$((SKIPPED + 1))
+    fi

同时,将整个脚本中的所有中文注释、输出消息和日志改为清晰的英文。

🧰 Tools
🪛 Shellcheck (0.11.0)

[error] 167-167: Parameter expansions can't start with {. Double check syntax.

(SC2296)


[error] 168-168: Parameter expansions can't start with {. Double check syntax.

(SC2296)


[warning] 212-212: project is referenced but not assigned.

(SC2154)


[error] 223-223: Parameter expansions can't start with {. Double check syntax.

(SC2296)


[error] 224-224: Parameter expansions can't start with {. Double check syntax.

(SC2296)

🤖 Prompt for AI Agents
In `@build/fix-groovy-compiler.sh` around lines 95 - 266, The script contains
Chinese text and uses an unquoted heredoc for the embedded Python block; replace
all Chinese comments and user-facing messages with English and switch the
heredoc to a single-quoted form (use <<'PYTHON_SCRIPT') so the shell will not
expand variables inside the block, and pass the FILE path into the Python script
via an exported environment variable (or as an argument) instead of relying on
unescaped expansions; update prints like "✅ 修复完成"/"⚠️  未找到需要替换的内容" and shell
echoes "✅ 修复成功"/"⚠️  修复失败或无需修复,请手动检查" to clear English equivalents, ensure the
Python logic (functions/variables: pattern, pattern2, replace_plugin,
lines/new_lines, version detection using version_line and version_match)
continues to work with the quoted heredoc and the existing escaped
${project.java.version} strings, and verify the script exits with the same
status behavior.

Comment on lines +1 to +98
#!/bin/bash
# Java 17 兼容性验证脚本
# 用于验证所有依赖在Java 17下的兼容性

set -e

echo "=========================================="
echo " Java 17 兼容性验证"
echo "=========================================="
echo ""

# 1. 检查Java版本
echo "[1/6] 检查Java版本..."
JAVA_VERSION=$(java -version 2>&1 | head -1 | cut -d'"' -f2 | sed '/^1\./s///' | cut -d'.' -f1)
if [ "$JAVA_VERSION" != "17" ]; then
echo "❌ 错误: 需要Java 17,当前版本: $JAVA_VERSION"
java -version
exit 1
fi
echo "✓ Java版本: $(java -version 2>&1 | head -1)"
echo ""

# 2. 检查关键依赖版本
echo "[2/6] 检查关键依赖版本..."
echo "检查Spring Framework版本..."
SPRING_VERSION=$(mvn help:evaluate -Dexpression=spring.framework.version -q -DforceStdout 2>/dev/null || echo "unknown")
echo " Spring Framework: $SPRING_VERSION"
if [[ "$SPRING_VERSION" == "5.2"* ]]; then
echo " ⚠️ 警告: Spring 5.2.x 非官方支持Java 17,建议升级到5.3.x"
fi

echo "检查Hibernate版本..."
HIBERNATE_VERSION=$(mvn help:evaluate -Dexpression=hibernate.version -q -DforceStdout 2>/dev/null || echo "unknown")
echo " Hibernate: $HIBERNATE_VERSION"

echo "检查AspectJ版本..."
ASPECTJ_VERSION=$(mvn help:evaluate -Dexpression=aspectj.version -q -DforceStdout 2>/dev/null || echo "unknown")
echo " AspectJ: $ASPECTJ_VERSION"

echo "检查Groovy版本..."
GROOVY_VERSION=$(mvn help:evaluate -Dexpression=groovy.version -q -DforceStdout 2>/dev/null || echo "unknown")
echo " Groovy: $GROOVY_VERSION"
echo ""

# 3. 编译测试(核心模块)
echo "[3/6] 编译核心模块 (core, header, utils)..."
if mvn clean compile -pl core,header,utils -DskipTests -q; then
echo "✓ 核心模块编译成功"
else
echo "❌ 错误: 核心模块编译失败"
exit 1
fi
echo ""

# 4. 编译测试(所有模块)
echo "[4/6] 编译所有模块..."
if mvn clean compile -DskipTests -q; then
echo "✓ 全量编译成功"
else
echo "❌ 错误: 全量编译失败"
exit 1
fi
echo ""

# 5. 单元测试(工具类)
echo "[5/6] 运行单元测试 (utils, abstraction)..."
if mvn test -pl utils,abstraction -Djacoco.skip=true -q 2>&1 | tee /tmp/java17-test.log; then
echo "✓ 单元测试通过"
else
TEST_FAILED=$(grep -c "FAILURE\|ERROR" /tmp/java17-test.log || echo "0")
if [ "$TEST_FAILED" -gt 0 ]; then
echo "⚠️ 警告: 部分单元测试失败,请检查日志"
else
echo "✓ 单元测试完成"
fi
fi
echo ""

# 6. 依赖兼容性检查
echo "[6/6] 检查依赖树(Spring/Hibernate相关)..."
echo "Spring相关依赖:"
mvn dependency:tree -Dincludes=org.springframework:* -q 2>/dev/null | grep -E "spring" | head -10 || echo " 无Spring依赖"
echo ""
echo "Hibernate相关依赖:"
mvn dependency:tree -Dincludes=org.hibernate:* -q 2>/dev/null | grep -E "hibernate" | head -10 || echo " 无Hibernate依赖"
echo ""

echo "=========================================="
echo " 验证完成"
echo "=========================================="
echo ""
echo "建议:"
if [[ "$SPRING_VERSION" == "5.2"* ]]; then
echo " - 考虑升级Spring Framework到5.3.23以获得官方Java 17支持"
fi
echo " - 运行完整集成测试: mvn test -pl test -Dtest=SomeBasicCase"
echo " - 检查运行时兼容性: 启动应用并测试核心功能"
echo ""
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

违反编码规范:脚本中包含大量中文文本

根据编码规范要求,代码中不应包含中文,包括报错、注释等都应使用正确的英文。此脚本中的注释和输出消息全部使用了中文,需要改为英文。

🛠️ 建议的修复方案(部分示例)
 #!/bin/bash
-# Java 17 兼容性验证脚本
-# 用于验证所有依赖在Java 17下的兼容性
+# Java 17 Compatibility Verification Script
+# Verifies compatibility of all dependencies with Java 17
 
 set -e
 
 echo "=========================================="
-echo "  Java 17 兼容性验证"
+echo "  Java 17 Compatibility Verification"
 echo "=========================================="
 echo ""
 
-# 1. 检查Java版本
-echo "[1/6] 检查Java版本..."
+# 1. Check Java version
+echo "[1/6] Checking Java version..."
 JAVA_VERSION=$(java -version 2>&1 | head -1 | cut -d'"' -f2 | sed '/^1\./s///' | cut -d'.' -f1)
 if [ "$JAVA_VERSION" != "17" ]; then
-    echo "❌ 错误: 需要Java 17,当前版本: $JAVA_VERSION"
+    echo "❌ Error: Java 17 required, current version: $JAVA_VERSION"
     java -version
     exit 1
 fi
-echo "✓ Java版本: $(java -version 2>&1 | head -1)"
+echo "✓ Java version: $(java -version 2>&1 | head -1)"
 echo ""
 
-# 2. 检查关键依赖版本
-echo "[2/6] 检查关键依赖版本..."
-echo "检查Spring Framework版本..."
+# 2. Check critical dependency versions
+echo "[2/6] Checking critical dependency versions..."
+echo "Checking Spring Framework version..."
 SPRING_VERSION=$(mvn help:evaluate -Dexpression=spring.framework.version -q -DforceStdout 2>/dev/null || echo "unknown")
 echo "  Spring Framework: $SPRING_VERSION"
 if [[ "$SPRING_VERSION" == "5.2"* ]]; then
-    echo "  ⚠️  警告: Spring 5.2.x 非官方支持Java 17,建议升级到5.3.x"
+    echo "  ⚠️  Warning: Spring 5.2.x does not officially support Java 17, recommend upgrading to 5.3.x"
 fi

其余中文文本同样需要翻译为英文。

根据编码规范:"代码里不应当有有中文,包括报错、注释等都应当使用正确的、无拼写错误的英文来写"。

🤖 Prompt for AI Agents
In `@build/verify-java17.sh` around lines 1 - 98, The script contains Chinese
comments and output strings; replace all Chinese text with clear English
equivalents (e.g., change header echoes, section markers "[1/6]".."[6/6]",
comment lines, error messages and suggestions) while preserving logic and
variable names like JAVA_VERSION, SPRING_VERSION, HIBERNATE_VERSION,
ASPECTJ_VERSION, GROOVY_VERSION and temp log /tmp/java17-test.log; update
messages for success/failure (e.g., "❌ 错误: 需要Java 17..." → "ERROR: Java 17 is
required, current version: ...", "✓ 核心模块编译成功" → "OK: core modules compiled") and
translate all other echoed guidance and comments to idiomatic, correctly spelled
English.

Comment on lines +1 to +18
# Java 17 模块系统配置
# 开放反射访问(226处setAccessible需要)
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.lang.reflect=ALL-UNNAMED
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.base/java.util.concurrent=ALL-UNNAMED
--add-opens java.base/java.io=ALL-UNNAMED
--add-opens java.base/java.net=ALL-UNNAMED

# 导出内部API(sun.misc.Signal需要)
--add-exports java.base/sun.misc=ALL-UNNAMED
--add-exports java.base/sun.security.ssl=ALL-UNNAMED

# Hibernate需要
--add-opens java.base/java.time=ALL-UNNAMED

# AspectJ需要
--add-opens java.base/java.lang.invoke=ALL-UNNAMED
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

配置文件注释请改为英文

当前注释为中文,请统一为英文。

As per coding guidelines: 代码里不应当有有中文,包括报错、注释等都应当使用正确的、无拼写错误的英文来写

🤖 Prompt for AI Agents
In `@conf/jvm-options-java17.conf` around lines 1 - 18, Replace all Chinese
comments in this JVM options file with clear English comments: change the file
header comment to "Java 17 module system configuration", the reflective-access
note "开放反射访问(226处setAccessible需要)" to "Open reflective access (required for
setAccessible usages)", the internal-API export note
"导出内部API(sun.misc.Signal需要)" to "Export internal APIs (required for
sun.misc.Signal)", the Hibernate note "Hibernate需要" to "Required by Hibernate",
and the AspectJ note "AspectJ需要" to "Required by AspectJ"; keep the option lines
(--add-opens, --add-exports, --add-opens java.base/java.time, --add-opens
java.base/java.lang.invoke) unchanged.

Comment on lines +1 to +1199
# ZStack 后端架构索引

> 本文档描述 ZStack IaaS 云计算平台后端的核心架构设计、组件机制和扩展方式。

## 目录

- [一、架构概览](#一架构概览)
- [二、核心框架层](#二核心框架层)
- [2.1 消息总线 (CloudBus)](#21-消息总线-cloudbus)
- [2.2 依赖注入与组件管理](#22-依赖注入与组件管理)
- [2.3 数据库访问层 (DatabaseFacade)](#23-数据库访问层-databasefacade)
- [2.4 异步编程模型 (ThreadFacade)](#24-异步编程模型-threadfacade)
- [2.5 错误处理机制 (ErrorFacade)](#25-错误处理机制-errorfacade)
- [三、API 消息设计](#三api-消息设计)
- [3.1 消息类型层次](#31-消息类型层次)
- [3.2 数据模型架构](#32-数据模型架构)
- [3.3 关键注解](#33-关键注解)
- [四、REST API 层](#四rest-api-层)
- [4.1 核心组件](#41-核心组件)
- [4.2 请求处理流程](#42-请求处理流程)
- [4.3 异步 API 机制](#43-异步-api-机制)
- [4.4 限流机制](#44-限流机制)
- [五、工作流引擎 (FlowChain)](#五工作流引擎-flowchain)
- [5.1 工作流类型](#51-工作流类型)
- [5.2 Flow 接口](#52-flow-接口)
- [5.3 回滚机制](#53-回滚机制)
- [六、插件扩展系统](#六插件扩展系统)
- [6.1 插件类型分类](#61-插件类型分类)
- [6.2 插件注册方式](#62-插件注册方式)
- [6.3 扩展点类型](#63-扩展点类型)
- [七、配置管理系统](#七配置管理系统)
- [7.1 GlobalConfig (全局配置)](#71-globalconfig-全局配置)
- [7.2 ResourceConfig (资源配置)](#72-resourceconfig-资源配置)
- [八、启动流程](#八启动流程)
- [8.1 启动入口](#81-启动入口)
- [8.2 管理节点启动步骤](#82-管理节点启动步骤)
- [8.3 数据库 Schema 管理](#83-数据库-schema-管理)
- [九、测试框架](#九测试框架)
- [9.1 测试框架结构](#91-测试框架结构)
- [9.2 模拟器机制](#92-模拟器机制)
- [9.3 环境定义 DSL](#93-环境定义-dsl)
- [十、架构特性总结](#十架构特性总结)

---

## 一、架构概览

ZStack 是一个企业级开源 IaaS 云计算平台,采用 **Java + Spring + JPA/Hibernate** 技术栈,以**消息总线**为核心构建分布式、可扩展的微服务架构。

### 核心设计理念

| 理念 | 说明 |
|------|------|
| **全异步架构** | 所有操作通过消息总线异步执行,提升系统吞吐量 |
| **插件化扩展** | 通过扩展点机制实现模块解耦,便于新功能接入 |
| **声明式 API** | 注解驱动的 REST API 设计,简化开发 |
| **统一错误处理** | AOP + 错误码体系,保证系统稳定性 |

### 核心技术栈

| 层次 | 技术 |
|------|------|
| 依赖注入 | Spring Framework |
| ORM | JPA / Hibernate |
| AOP | AspectJ |
| 消息传输 | HTTP / RabbitMQ |
| 测试框架 | JUnit + Groovy DSL |
| 数据库迁移 | Flyway |
| 连接池 | C3P0 |

### 项目模块结构

```
zstack/
├── core/ # 核心框架(消息总线、数据库、线程、错误处理)
├── header/ # API 消息定义、VO/Inventory、常量枚举
├── rest/ # REST API 服务器
├── portal/ # 管理节点启动入口
├── configuration/ # 配置管理
├── resourceconfig/ # 资源级配置
├── compute/ # 计算资源管理
├── storage/ # 存储资源管理
├── network/ # 网络资源管理
├── identity/ # 身份认证
├── image/ # 镜像管理
├── plugin/ # 插件模块(KVM、Ceph、虚拟路由器等)
├── test/ # 集成测试
├── testlib/ # 测试框架库
├── simulator/ # 模拟器
└── conf/ # 配置文件和数据库 Schema
```

---

## 二、核心框架层

核心框架位于 `core/src/main/java/org/zstack/core/` 目录。

### 2.1 消息总线 (CloudBus)

消息总线是 ZStack 架构的核心,负责服务间通信。

#### 核心类

| 类/接口 | 路径 | 说明 |
|---------|------|------|
| `CloudBus` | `core/cloudbus/CloudBus.java` | 消息总线核心接口 |
| `CloudBusImpl3` | `core/cloudbus/CloudBusImpl3.java` | 当前实现(基于 HTTP) |
| `CloudBusImpl2` | `core/cloudbus/CloudBusImpl2.java` | 旧实现(基于 RabbitMQ) |

#### 消息类型层次

```
Message (基础消息)
└── NeedReplyMessage (需要回复的消息)
└── APIMessage (API 消息)
├── APICreateMessage (创建资源)
├── APIGetMessage (获取资源)
└── APIQueryMessage (查询资源)

MessageReply (消息回复)
└── APIReply (API 回复)

Event (事件)
└── APIEvent (API 事件)
```

#### 核心功能

| 方法 | 说明 |
|------|------|
| `send(msg, callback)` | 异步发送消息,通过回调接收响应 |
| `call(msg)` | 同步调用,阻塞等待回复 |
| `route(msg)` | 根据 `serviceId` 路由消息到对应服务 |
| `publish(event)` | 发布事件给所有订阅者 |
| `registerService(service)` | 注册服务到消息总线 |

#### 使用示例

```java
// 异步发送
bus.send(msg, new CloudBusCallBack(completion) {
@Override
public void run(MessageReply reply) {
if (reply.isSuccess()) {
// 处理成功
} else {
// 处理失败
}
}
});

// 同步调用
MessageReply reply = bus.call(msg);
```

### 2.2 依赖注入与组件管理

#### 核心类

| 类/接口 | 路径 | 说明 |
|---------|------|------|
| `ComponentLoader` | `core/componentloader/ComponentLoader.java` | 组件加载器接口 |
| `ComponentLoaderImpl` | `core/componentloader/ComponentLoaderImpl.java` | 基于 Spring 的实现 |
| `PluginRegistry` | `core/componentloader/PluginRegistry.java` | 插件注册表接口 |
| `PluginRegistryImpl` | `core/componentloader/PluginRegistryImpl.java` | 插件注册实现 |
| `Platform` | `core/Platform.java` | 平台入口(静态访问) |

#### 组件生命周期接口

```java
// 组件接口 - 定义生命周期
public interface Component {
boolean start(); // 启动
boolean stop(); // 停止
}

// 服务接口 - 可处理消息
public interface Service extends Component {
void handleMessage(Message msg); // 处理消息
String getId(); // 服务 ID
int getSyncLevel(); // 同步级别
List<String> getAliasIds(); // 别名
}
```

#### 获取组件

```java
// 通过 Platform 静态方法获取
DatabaseFacade dbf = Platform.getComponentLoader().getComponent(DatabaseFacade.class);

// 或使用 Spring 注入
@Autowired
private DatabaseFacade dbf;
```

### 2.3 数据库访问层 (DatabaseFacade)

#### 核心类

| 类/接口 | 路径 | 说明 |
|---------|------|------|
| `DatabaseFacade` | `core/db/DatabaseFacade.java` | 数据库门面接口 |
| `DatabaseFacadeImpl` | `core/db/DatabaseFacadeImpl.java` | JPA/Hibernate 实现 |
| `SQLBatch` | `core/db/SQLBatch.java` | SQL 批处理抽象类 |
| `SimpleQuery` | `core/db/SimpleQuery.java` | 查询构建器 |
| `SQL` | `core/db/SQL.java` | SQL 工具类 |

#### 核心功能

| 方法 | 说明 |
|------|------|
| `persist(entity)` | 持久化实体 |
| `update(entity)` | 更新实体 |
| `remove(entity)` | 删除实体(支持软删除) |
| `findByUuid(uuid, class)` | 按 UUID 查找 |
| `createQuery(class)` | 创建查询 |
| `eoCleanup(class)` | 清理软删除的实体对象 |

#### 软删除机制

```java
// VO 定义时使用 @EO 注解指定软删除实体
@Entity
@Table
@EO(EOClazz = VmInstanceEO.class)
public class VmInstanceVO extends VmInstanceAO {
// ...
}

// 软删除级联
@Entity
@SoftDeletionCascades({
@SoftDeletionCascade(parent = VmInstanceVO.class, joinColumn = "vmInstanceUuid")
})
public class VmNicVO extends VmNicAO {
// ...
}
```

#### 死锁处理

```java
// 使用 @DeadlockAutoRestart 自动重试
@DeadlockAutoRestart
public void updateResource() {
// 可能发生死锁的操作
}
```

### 2.4 异步编程模型 (ThreadFacade)

#### 核心类

| 类/接口 | 路径 | 说明 |
|---------|------|------|
| `ThreadFacade` | `core/thread/ThreadFacade.java` | 线程管理门面接口 |
| `ThreadFacadeImpl` | `core/thread/ThreadFacadeImpl.java` | 线程池实现 |
| `Completion` | `header/core/Completion.java` | 无返回值回调 |
| `ReturnValueCompletion` | `header/core/ReturnValueCompletion.java` | 有返回值回调 |
| `NoErrorCompletion` | `header/core/NoErrorCompletion.java` | 无错误回调 |

#### 回调接口层次

```
AbstractCompletion (抽象基类)
├── Completion (无返回值回调)
│ ├── success()
│ └── fail(ErrorCode)
├── ReturnValueCompletion<T> (有返回值回调)
│ ├── success(T returnValue)
│ └── fail(ErrorCode)
└── NoErrorCompletion (无错误回调)
└── done()
```

#### 异步执行注解

| 注解 | 说明 |
|------|------|
| `@AsyncThread` | 异步执行方法 |
| `@SyncThread` | 同步执行(带同步级别) |
| `@ScheduledThread` | 定时执行 |

#### 同步控制机制

```java
// SyncTask 接口 - 支持同步级别
public interface SyncTask<T> extends Task<T> {
String getSyncSignature(); // 同步签名,相同签名的任务串行执行
int getSyncLevel(); // 同步级别
}

// 使用示例
thdf.syncSubmit(new SyncTask<Void>() {
@Override
public String getSyncSignature() {
return "host-" + hostUuid; // 同一主机的操作串行执行
}

@Override
public Void call() throws Exception {
// 执行操作
return null;
}
});
```

#### SingleFlight 模式

```java
// 相同 key 的请求只执行一次
thdf.singleFlightSubmit(new SingleFlightTask(completion) {
@Override
public String getSingleFlightKey() {
return "refresh-" + resourceUuid;
}

@Override
public void run(SingleFlightCompletion completion) {
// 执行操作
completion.success(result);
}
});
```

### 2.5 错误处理机制 (ErrorFacade)

#### 核心类

| 类/接口 | 路径 | 说明 |
|---------|------|------|
| `ErrorFacade` | `core/errorcode/ErrorFacade.java` | 错误处理门面接口 |
| `ErrorFacadeImpl` | `core/errorcode/ErrorFacadeImpl.java` | 错误处理实现 |
| `ErrorCode` | `header/errorcode/ErrorCode.java` | 错误码类 |
| `OperationFailureException` | `header/exception/OperationFailureException.java` | 操作失败异常 |

#### AOP 异常处理注解

| 注解 | 切面 | 说明 |
|------|------|------|
| `@ExceptionSafe` | `ExceptionSafeAspect.aj` | 自动捕获异常 |
| `@MessageSafe` | `MessageSafeAspect.aj` | 消息处理异常自动回复 |

#### 错误码定义

```xml
<!-- XML 定义错误码 -->
<error>
<code>SYS.1001</code>
<description>Internal error</description>
</error>
```

#### 异常处理流程

```
方法抛出异常
AOP 拦截 (ExceptionSafeAspect / MessageSafeAspect)
转换为 ErrorCode
通过 Completion.fail(ErrorCode) 回调
通过 bus.replyErrorByMessageType() 回复消息
```

---

## 三、API 消息设计

API 消息定义位于 `header/src/main/java/org/zstack/header/` 目录。

### 3.1 消息类型层次

| 类型 | 命名模式 | 基类 | 用途 |
|------|---------|------|------|
| 请求消息 | `API*Msg` | `APIMessage` | 客户端请求 |
| 异步事件 | `API*Event` | `APIEvent` | 异步操作结果通知 |
| 同步回复 | `API*Reply` | `APIReply` | 同步查询结果 |

#### 消息流模式

```
同步 API: APIMessage → CloudBus.call() → APIReply
异步 API: APIMessage → CloudBus.send() → APIEvent (通过回调)

资源操作模式:
- Create: APICreateXxxMsg → APICreateXxxEvent
- Update: APIUpdateXxxMsg → APIUpdateXxxEvent
- Delete: APIDeleteXxxMsg → APIDeleteXxxEvent
- Query: APIQueryXxxMsg → APIQueryXxxReply
- Get: APIGetXxxMsg → APIGetXxxReply
```

### 3.2 数据模型架构

#### 层次结构

```
AO (Abstract Object) - 定义字段
└── VO (Value Object) - 数据库实体
└── EO (Entity Object) - 软删除视图

数据流转:
VO (数据库实体) → Inventory (DTO) → API Response (REST 响应)
```

#### VO 定义示例

```java
@Entity
@Table
@EO(EOClazz = VmInstanceEO.class)
@BaseResource
@EntityGraph(
parents = {
@EntityGraph.Neighbour(type = ZoneVO.class, myField = "zoneUuid", targetField = "uuid"),
},
friends = {
@EntityGraph.Neighbour(type = ImageVO.class, myField = "imageUuid", targetField = "uuid"),
}
)
public class VmInstanceVO extends VmInstanceAO implements OwnedByAccount, ToInventory {
// 字段定义
}
```

#### Inventory 定义示例

```java
@Inventory(mappingVOClass = VmInstanceVO.class)
@ExpandedQueries({
@ExpandedQuery(expandedField = "zone", inventoryClass = ZoneInventory.class, foreignKey = "zoneUuid")
})
public class VmInstanceInventory implements Serializable {

public static VmInstanceInventory valueOf(VmInstanceVO vo) {
return new VmInstanceInventory(vo);
}

public static List<VmInstanceInventory> valueOf(Collection<VmInstanceVO> vos) {
return vos.stream().map(VmInstanceInventory::valueOf).collect(Collectors.toList());
}
}
```

### 3.3 关键注解

#### @RestRequest

```java
@RestRequest(
path = "/vm-instances",
method = HttpMethod.POST,
responseClass = APICreateVmInstanceEvent.class,
parameterName = "params"
)
public class APICreateVmInstanceMsg extends APICreateMessage {
// ...
}
```

#### @APIParam

```java
@APIParam(
required = true, // 必填
maxLength = 255, // 最大长度
resourceType = ZoneVO.class, // 资源类型验证
checkAccount = true, // 账户权限检查
validValues = {"Enabled", "Disabled"} // 有效值
)
private String zoneUuid;
```

#### @RestResponse

```java
@RestResponse(allTo = "inventory") // 所有字段映射到 inventory
public class APICreateVmInstanceEvent extends APIEvent {
private VmInstanceInventory inventory;
}
```

---

## 四、REST API 层

REST API 实现位于 `rest/src/main/java/org/zstack/rest/` 目录。

### 4.1 核心组件

| 类 | 说明 |
|----|------|
| `RestServer` | REST API 服务器核心实现 |
| `RestServerController` | Spring MVC 入口控制器 |
| `RateLimiter` | 限流器(Token Bucket 算法) |
| `AsyncRestApiStore` | 异步 API 存储接口 |
| `MysqlAsyncRestStore` | 异步 API 存储实现 |
| `RequestData` | 请求数据封装 |

### 4.2 请求处理流程

```
HTTP 请求
RestServerController (Spring MVC 入口)
RestServer.handle()
┌─────────────────────────────────┐
│ 1. 限流检查 (RateLimiter) │
│ 2. 请求拦截器处理 │
│ 3. 路径匹配 (AntPathMatcher) │
│ 4. 认证处理 │
│ ├─ OAuth: Authorization: OAuth <token>
│ └─ AccessKey: Authorization: ZStack <id>:<signature>
│ 5. 参数解析 │
│ ├─ GET/DELETE: Query String │
│ └─ POST/PUT: Request Body │
│ 6. 构建 APIMessage │
│ 7. 发送到 CloudBus │
│ ├─ 同步调用 → 200 OK │
│ └─ 异步调用 → 202 Accepted │
└─────────────────────────────────┘
```

### 4.3 异步 API 机制

#### 工作流程

```
1. 请求提交
POST /v1/vm-instances
→ 保存到 AsyncRestVO (状态: processing)
→ 发送消息到 CloudBus
→ 返回 202 Accepted
→ Location: /v1/api-jobs/{uuid}

2. 状态查询
GET /v1/api-jobs/{uuid}
→ processing: 202 Accepted
→ done + success: 200 OK
→ done + failed: 503 Service Unavailable
→ expired: 404 Not Found

3. WebHook 回调 (可选)
请求头: X-Web-Hook: <url>
任务完成时自动 POST 结果到 WebHook URL
```

### 4.4 限流机制

基于 Token Bucket 算法实现:

```java
public class RateLimiter {
private final LoadingCache<String, TokenBucket> requestCache;
private final int maxRequestsPerMinute;

// 基于客户端 IP 限流
public boolean isRateLimitExceeded(String clientIp) {
TokenBucket bucket = requestCache.get(clientIp);
return !bucket.tryConsume();
}
}
```

配置:
- 默认限制:12000 请求/分钟
- 可通过 `RestGlobalProperty.REST_RATE_LIMITS` 配置

---

## 五、工作流引擎 (FlowChain)

工作流实现位于 `core/src/main/java/org/zstack/core/workflow/` 目录。

### 5.1 工作流类型

| 类型 | 特点 | 适用场景 |
|------|------|---------|
| `SimpleFlowChain` | 内存执行,无持久化,性能好 | 简单业务流程 |
| `WorkFlowChain` | 数据库持久化,支持恢复 | 复杂流程,需状态恢复 |
| `AsyncWorkFlowChain` | 异步执行和回调 | 异步场景 |
| `ShareFlowChain` | 动态流程编排 | 运行时构建流程 |

### 5.2 Flow 接口

```java
public interface Flow {
// 执行流程
void run(FlowTrigger trigger, Map data);

// 回滚流程
void rollback(FlowRollback trigger, Map data);

// 是否跳过(默认不跳过)
default boolean skip(Map data) { return false; }
}

public interface FlowTrigger {
void next(); // 继续下一个流程
void fail(ErrorCode err); // 失败并触发回滚
}

public interface FlowRollback {
void rollback(); // 继续回滚
void skipRestRollbacks(); // 跳过剩余回滚
}
```

#### 使用示例

```java
new SimpleFlowChain()
.setName("create-vm-flow")
.then(new Flow() {
@Override
public void run(FlowTrigger trigger, Map data) {
// 步骤 1:分配资源
data.put("allocated", true);
trigger.next();
}

@Override
public void rollback(FlowRollback trigger, Map data) {
// 回滚:释放资源
releaseResource();
trigger.rollback();
}
})
.then(new NoRollbackFlow() {
@Override
public void run(FlowTrigger trigger, Map data) {
// 步骤 2:启动 VM(无需回滚)
trigger.next();
}
})
.done(new FlowDoneHandler(completion) {
@Override
public void handle(Map data) {
// 完成处理
completion.success();
}
})
.error(new FlowErrorHandler(completion) {
@Override
public void handle(ErrorCode err, Map data) {
// 错误处理
completion.fail(err);
}
})
.start();
```

### 5.3 回滚机制

#### SimpleFlowChain 回滚

- 使用栈结构(LIFO)实现逆序回滚
- 调用 `trigger.next()` 时,当前 Flow 入栈
- 调用 `trigger.fail(err)` 时,触发回滚
- 支持 `skipRestRollbacks()` 跳过剩余回滚

#### WorkFlowChain 回滚

- 每个 Flow 状态持久化到 `WorkFlowVO`
- 支持 `carryOn(chainUuid)` 从断点继续执行
- 自动判断继续策略:重启或继续回滚

---

## 六、插件扩展系统

插件位于 `plugin/` 目录,每个插件是独立的 Maven 模块。

### 6.1 插件类型分类

| 类型 | 插件 | 说明 |
|------|------|------|
| **Hypervisor** | `kvm` | KVM 虚拟化 |
| **Primary Storage** | `nfsPrimaryStorage`, `localstorage`, `ceph`, `sharedMountPointPrimaryStorage` | 主存储 |
| **Backup Storage** | `sftpBackupStorage` | 备份存储 |
| **Network Provider** | `virtualRouterProvider`, `flatNetworkProvider`, `sdnController` | 网络服务提供者 |
| **Network Service** | `securityGroup`, `portForwarding`, `loadBalancer`, `eip`, `vip` | 网络服务 |
| **认证/授权** | `ldap`, `loginPlugin`, `directory` | 身份认证 |

### 6.2 插件注册方式

#### XML 配置方式

```xml
<!-- 在 Spring XML 中注册插件 -->
<bean id="KVMHostFactory" class="org.zstack.kvm.KVMHostFactory">
<zstack:plugin>
<zstack:extension interface="org.zstack.header.host.HypervisorFactory" />
<zstack:extension interface="org.zstack.header.Component" />
<zstack:extension interface="org.zstack.header.Service" />
</zstack:plugin>
</bean>
```

#### 代码方式 (PluginDSL)

```java
// 使用 PluginDSL 在代码中声明扩展
PluginDSL.plugin("myPlugin")
.extension(SomeExtensionPoint.class, this)
.register();
```

### 6.3 扩展点类型

#### Factory 模式

```java
// 定义工厂接口
public interface HypervisorFactory {
HypervisorType getHypervisorType();
Host createHost(HostVO vo);
}

// 实现工厂
public class KVMHostFactory implements HypervisorFactory {
public static final HypervisorType hypervisorType = new HypervisorType("KVM");

@Override
public HypervisorType getHypervisorType() {
return hypervisorType;
}
}

// 使用工厂
List<HypervisorFactory> factories = pluginRgty.getExtensionList(HypervisorFactory.class);
```

#### Backend 模式

```java
// 定义后端接口
public interface PortForwardingBackend {
NetworkServiceProviderType getProviderType();
void applyPortForwardingRule(PortForwardingRuleInventory rule, Completion completion);
}

// 实现后端
public class VirtualRouterPortForwardingBackend implements PortForwardingBackend {
@Override
public NetworkServiceProviderType getProviderType() {
return VirtualRouterConstant.VIRTUAL_ROUTER_PROVIDER_TYPE;
}
}

// 注册和使用
Map<String, PortForwardingBackend> backends = new HashMap<>();
for (PortForwardingBackend backend : pluginRgty.getExtensionList(PortForwardingBackend.class)) {
backends.put(backend.getProviderType().toString(), backend);
}
```

#### ExtensionPoint 回调模式

```java
// 定义扩展点接口
public interface KVMStartVmExtensionPoint {
void beforeStartVmOnKvm(KVMHostInventory host, VmInstanceSpec spec, StartVmCmd cmd);
void afterStartVmOnKvm(KVMHostInventory host, VmInstanceSpec spec, StartVmCmd cmd);
}

// 实现扩展点
public class CephPrimaryStorageFactory implements KVMStartVmExtensionPoint {
@Override
public void beforeStartVmOnKvm(KVMHostInventory host, VmInstanceSpec spec, StartVmCmd cmd) {
// 在 VM 启动前执行 Ceph 相关逻辑
}
}

// 触发扩展点
for (KVMStartVmExtensionPoint ext : pluginRgty.getExtensionList(KVMStartVmExtensionPoint.class)) {
ext.beforeStartVmOnKvm(host, spec, cmd);
}
```

---

## 七、配置管理系统

### 7.1 GlobalConfig (全局配置)

位于 `core/src/main/java/org/zstack/core/config/` 目录。

#### 核心类

| 类 | 说明 |
|----|------|
| `GlobalConfig` | 配置项实体 |
| `GlobalConfigFacade` | 配置管理门面接口 |
| `GlobalConfigFacadeImpl` | 配置管理实现 |
| `GlobalConfigVO` | 配置数据库实体 |

#### 配置来源优先级

```
1. 数据库(运行时值)
2. XML 配置文件(conf/globalConfig/*.xml)
3. Java 注解(@GlobalConfigDef)
4. 扩展点自动生成
```

#### 定义方式

**XML 方式**:
```xml
<!-- conf/globalConfig/xxx.xml -->
<globalConfig>
<config>
<category>vm</category>
<name>vm.cleanTraffic</name>
<description>Whether to clean traffic when deleting VM</description>
<defaultValue>false</defaultValue>
<type>java.lang.Boolean</type>
</config>
</globalConfig>
```

**注解方式**:
```java
@GlobalConfigDefinition
public class VmGlobalConfig {
public static final String CATEGORY = "vm";

@GlobalConfigValidation(notNull = true)
public static GlobalConfig VM_CLEAN_TRAFFIC = new GlobalConfig(CATEGORY, "vm.cleanTraffic");
}
```

#### 使用方式

```java
// 获取配置值
boolean cleanTraffic = VmGlobalConfig.VM_CLEAN_TRAFFIC.value(Boolean.class);

// 监听配置变更
VmGlobalConfig.VM_CLEAN_TRAFFIC.installUpdateExtension((oldValue, newValue) -> {
// 处理配置变更
});
```

### 7.2 ResourceConfig (资源配置)

位于 `resourceconfig/src/main/java/org/zstack/resourceconfig/` 目录。

#### 与 GlobalConfig 的区别

| 特性 | GlobalConfig | ResourceConfig |
|------|-------------|----------------|
| 作用域 | 全局共享 | 资源级独立设置 |
| 存储 | `GlobalConfigVO` | `ResourceConfigVO` |
| 查找顺序 | 直接返回全局值 | 资源 → 父资源 → GlobalConfig |

#### 绑定方式

```java
@GlobalConfigDefinition
public class VmGlobalConfig {
// 绑定到 VM 和 Cluster,支持多级继承
@BindResourceConfig({VmInstanceVO.class, ClusterVO.class})
public static GlobalConfig VM_VIDEO_TYPE = new GlobalConfig(CATEGORY, "videoType");
}
```

#### 查找优先级

```
1. 资源自身配置(ResourceConfigVO 中 resourceUuid 匹配)
2. 父资源配置(通过 DBGraph 查找父资源)
3. GlobalConfig 默认值
```

#### API 接口

| API | 说明 |
|-----|------|
| `APIUpdateResourceConfigMsg` | 更新资源配置 |
| `APIDeleteResourceConfigMsg` | 删除资源配置 |
| `APIGetResourceConfigMsg` | 获取资源配置 |
| `APIGetResourceBindableConfigMsg` | 查询可绑定的配置 |

---

## 八、启动流程

### 8.1 启动入口

```
Servlet 容器启动
BootstrapWebListener.contextInitialized()
→ 触发 Platform 静态初始化
BootstrapContextLoaderListener.contextInitialized()
→ 加载 Spring 上下文 (beanRefContext.xml → zstack.xml)
ComponentLoaderWebListener.contextInitialized()
→ Platform.createComponentLoaderFromWebApplicationContext()
→ ManagementNodeManager.startNode()
```

### 8.2 管理节点启动步骤

`ManagementNodeManagerImpl.start()` 使用 FlowChain 编排启动步骤:

| 步骤 | 名称 | 说明 |
|------|------|------|
| 1 | `bootstrap-cloudbus` | CloudBus 初始化(已在 Platform 中完成) |
| 2 | `populate-components` | 收集所有 Component 和 Service |
| 3 | `register-node-on-cloudbus` | 注册管理节点服务到 CloudBus |
| 4 | `call-prepare-db-extension` | 准备数据库初始值 |
| 5 | `start-components` | 启动所有组件和服务 |
| 6 | `create-DB-record` | 创建/更新 ManagementNodeVO |
| 7 | `start-heartbeat` | 启动心跳机制 |
| 8 | `start-api-mediator` | 启动 API 中介器 |
| 9 | `set-node-to-running` | 设置节点状态为 RUNNING |
| 10 | `I-join` | 触发节点加入事件 |
| 11 | `node-is-ready` | 调用 ManagementNodeReadyExtensionPoint |
| 12 | `listen-node-life-cycle-events` | 监听节点生命周期事件 |
| 13 | `say-I-join` | 通知其他管理节点 |

### 8.3 数据库 Schema 管理

#### Flyway 迁移

```bash
# 部署脚本 (conf/deploydb.sh)
1. 创建数据库(如果不存在)
2. 复制 SQL 文件到 Flyway 目录
- conf/db/V0.6__schema.sql (基础 schema)
- conf/db/upgrade/*.sql (升级脚本)
3. Flyway clean (清理)
4. Flyway baseline (创建 baseline)
5. Flyway migrate (执行迁移,outOfOrder=true)
```

#### Schema 文件结构

```
conf/db/
├── V0.6__schema.sql # 基础 schema
├── upgrade/
│ ├── V2.5.0__schema.sql # 升级脚本
│ ├── V3.9.0__schema.sql
│ └── ...
├── beforeMigrate.sql # 迁移前执行
└── beforeValidate.sql # 验证前执行
```

---

## 九、测试框架

测试框架位于 `test/` 和 `testlib/` 目录。

### 9.1 测试框架结构

```groovy
// 测试用例基类
class ExampleCase extends SubCase {
EnvSpec env

@Override
void setup() {
// 配置 Spring,指定需要的服务模块
spring {
sftpBackupStorage()
localStorage()
kvm()
}
}

@Override
void environment() {
// 定义测试环境
env = env {
zone {
name = "zone1"
cluster { ... }
}
}
}

@Override
void test() {
// 创建环境并执行测试
env.create {
testMethod1()
testMethod2()
}
}

@Override
void clean() {
env.delete()
}
}
```

#### 生命周期

```
setup() → 配置 Spring,指定模块
environment() → 定义测试环境(不创建)
test() → env.create() 创建环境,执行测试
clean() → 清理环境
```

### 9.2 模拟器机制

模拟器拦截 Agent HTTP 请求,返回模拟响应。

```groovy
class KVMSimulator implements Simulator {
@Override
void registerSimulators(EnvSpec spec) {
// 注册 KVM Agent 路径处理器
spec.simulator(KVMConstant.KVM_HOST_CAPACITY_PATH) { HttpEntity<String> e, EnvSpec espec ->
def rsp = new KVMAgentCommands.HostCapacityResponse()
rsp.success = true
rsp.cpuNum = 8
rsp.totalMemory = SizeUnit.GIGABYTE.toByte(32)
return rsp
}

spec.simulator(KVMConstant.KVM_START_VM_PATH) { HttpEntity<String> e ->
def cmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.StartVmCmd.class)
def rsp = new KVMAgentCommands.StartVmResponse()
rsp.success = true
return rsp
}
}
}
```

#### 处理器类型

| 类型 | 说明 |
|------|------|
| `simulator(path, closure)` | 基础 HTTP 处理器 |
| `preSimulator(path, closure)` | 请求前处理器 |
| `afterSimulator(path, closure)` | 请求后处理器 |
| `hijackSimulator(path, closure)` | 最终拦截处理器 |

### 9.3 环境定义 DSL

```groovy
env = env {
zone {
name = "zone"
cluster {
name = "cluster"
kvmHost {
name = "host"
totalCpu = 8
totalMem = SizeUnit.GIGABYTE.toByte(32)
}
}
instanceOffering {
name = "instanceOffering"
cpuNum = 2
memorySize = SizeUnit.GIGABYTE.toByte(4)
}
diskOffering {
name = "diskOffering"
diskSize = SizeUnit.GIGABYTE.toByte(100)
}
sftpBackupStorage {
name = "bs"
url = "/tmp/bs"
}
nfsPrimaryStorage {
name = "ps"
url = "/tmp/ps"
}
l2NoVlanNetwork {
name = "l2"
l3Network {
name = "l3"
ip {
startIp = "192.168.0.2"
endIp = "192.168.0.254"
gateway = "192.168.0.1"
netmask = "255.255.255.0"
}
}
}
vm {
name = "vm"
useHost("host")
useL3Networks("l3")
useInstanceOffering("instanceOffering")
useImage("image")
}
}
}
```

---

## 十、架构特性总结

### 设计模式应用

| 模式 | 应用场景 |
|------|---------|
| **门面模式** | `DatabaseFacade`, `ThreadFacade`, `ErrorFacade`, `GlobalConfigFacade` |
| **工厂模式** | `HypervisorFactory`, `PrimaryStorageFactory`, `BackupStorageFactory` |
| **观察者模式** | 事件订阅机制、配置变更监听 |
| **策略模式** | 插件扩展点、Backend 接口 |
| **模板方法** | `SQLBatch`, `AbstractService`, `Flow` |
| **责任链模式** | `FlowChain` 工作流 |
| **代理模式** | AOP 增强(AspectJ) |

### 架构优势

| 优势 | 说明 |
|------|------|
| **高度解耦** | 消息总线 + 插件系统实现模块解耦 |
| **可扩展性** | 扩展点机制便于新功能接入 |
| **异步优先** | 全异步架构提升系统吞吐量 |
| **容错性** | FlowChain 支持回滚和状态恢复 |
| **统一抽象** | 门面模式简化复杂性 |
| **测试友好** | 模拟器机制便于集成测试 |
| **配置灵活** | GlobalConfig + ResourceConfig 多级配置 |

### 关键目录索引

| 目录 | 内容 |
|------|------|
| `core/` | 核心框架(消息总线、数据库、线程、错误处理) |
| `header/` | API 消息定义、VO/Inventory、常量枚举 |
| `rest/` | REST API 服务器 |
| `portal/` | 管理节点启动入口 |
| `configuration/` | 配置管理 |
| `resourceconfig/` | 资源级配置 |
| `plugin/` | 插件模块 |
| `test/` | 集成测试 |
| `testlib/` | 测试框架库 |
| `conf/` | 配置文件和数据库 Schema |

---

## 附录:常用类速查

### 核心框架

| 类 | 包路径 | 用途 |
|----|--------|------|
| `Platform` | `org.zstack.core` | 平台入口,静态访问组件 |
| `CloudBus` | `org.zstack.core.cloudbus` | 消息总线 |
| `DatabaseFacade` | `org.zstack.core.db` | 数据库访问 |
| `ThreadFacade` | `org.zstack.core.thread` | 线程管理 |
| `ErrorFacade` | `org.zstack.core.errorcode` | 错误处理 |
| `PluginRegistry` | `org.zstack.core.componentloader` | 插件注册 |
| `GlobalConfigFacade` | `org.zstack.core.config` | 全局配置 |

### API 相关

| 类 | 包路径 | 用途 |
|----|--------|------|
| `APIMessage` | `org.zstack.header.message` | API 消息基类 |
| `APIEvent` | `org.zstack.header.message` | API 事件基类 |
| `APIReply` | `org.zstack.header.message` | API 回复基类 |
| `RestServer` | `org.zstack.rest` | REST API 服务器 |

### 工作流

| 类 | 包路径 | 用途 |
|----|--------|------|
| `SimpleFlowChain` | `org.zstack.core.workflow` | 简单工作流 |
| `Flow` | `org.zstack.header.core.workflow` | 流程接口 |
| `FlowTrigger` | `org.zstack.header.core.workflow` | 流程触发器 |

### 回调

| 类 | 包路径 | 用途 |
|----|--------|------|
| `Completion` | `org.zstack.header.core` | 无返回值回调 |
| `ReturnValueCompletion` | `org.zstack.header.core` | 有返回值回调 |
| `NoErrorCompletion` | `org.zstack.header.core` | 无错误回调 |

---

*文档版本: 1.0*
*最后更新: 2025-01*
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

请确认文档语言是否符合仓库规范

当前文档为中文,若仓库要求英文文档,请补充英文版本或改为英文。

As per coding guidelines: 代码里不应当有有中文,包括报错、注释等都应当使用正确的、无拼写错误的英文来写

🧰 Tools
🪛 markdownlint-cli2 (0.20.0)

[warning] 53-53: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 53-53: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 53-53: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 53-53: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 62-62: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 62-62: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 62-62: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 62-62: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 73-73: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


[warning] 106-106: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 106-106: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 106-106: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 106-106: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 106-106: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 106-106: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 113-113: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


[warning] 131-131: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 131-131: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 131-131: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 131-131: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 162-162: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 162-162: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 162-162: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 162-162: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 162-162: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 162-162: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 203-203: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 203-203: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 203-203: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 203-203: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 203-203: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 203-203: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 213-213: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 213-213: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 213-213: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 213-213: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 257-257: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 257-257: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 257-257: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 257-257: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 257-257: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 257-257: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 266-266: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


[warning] 283-283: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 283-283: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 283-283: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 283-283: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 335-335: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 335-335: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 335-335: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 335-335: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 335-335: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 335-335: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 345-345: Table column style
Table pipe does not align with header for style "aligned"

(MD060, table-column-style)


[warning] 345-345: Table column style
Table pipe does not align with header for style "aligned"

(MD060, table-column-style)


[warning] 345-345: Table column style
Table pipe does not align with header for style "aligned"

(MD060, table-column-style)


[warning] 346-346: Table column style
Table pipe does not align with header for style "aligned"

(MD060, table-column-style)


[warning] 346-346: Table column style
Table pipe does not align with header for style "aligned"

(MD060, table-column-style)


[warning] 346-346: Table column style
Table pipe does not align with header for style "aligned"

(MD060, table-column-style)


[warning] 360-360: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


[warning] 381-381: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 381-381: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 381-381: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 381-381: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 381-381: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 381-381: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 381-381: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 381-381: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 388-388: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


[warning] 404-404: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


[warning] 499-499: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 499-499: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 499-499: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 499-499: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 509-509: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


[warning] 537-537: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


[warning] 587-587: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 587-587: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 587-587: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 587-587: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 587-587: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 587-587: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 686-686: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 686-686: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 686-686: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 686-686: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 686-686: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 686-686: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 801-801: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 801-801: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 801-801: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 801-801: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 809-809: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


[warning] 862-862: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 862-862: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 862-862: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 862-862: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 862-862: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 862-862: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 880-880: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


[warning] 889-889: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 889-889: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 889-889: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 889-889: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 901-901: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


[warning] 920-920: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 920-920: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 920-920: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 920-920: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 920-920: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 920-920: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 952-952: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


[warning] 1015-1015: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


[warning] 1052-1052: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 1052-1052: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 1052-1052: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 1052-1052: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 1119-1119: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 1119-1119: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 1119-1119: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 1119-1119: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 1131-1131: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 1131-1131: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 1131-1131: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 1131-1131: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 1143-1143: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 1143-1143: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 1143-1143: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 1143-1143: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 1162-1162: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 1162-1162: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 1162-1162: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 1162-1162: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 1162-1162: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 1162-1162: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 1174-1174: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 1174-1174: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 1174-1174: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 1174-1174: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 1174-1174: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 1174-1174: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 1183-1183: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 1183-1183: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 1183-1183: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 1183-1183: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 1183-1183: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 1183-1183: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 1191-1191: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 1191-1191: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 1191-1191: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 1191-1191: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 1191-1191: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 1191-1191: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)

🤖 Prompt for AI Agents
In `@docs/ARCHITECTURE.md` around lines 1 - 1199, The document "ZStack 后端架构索引" is
in Chinese but the repo requires English-only content; update this
ARCHITECTURE.md by providing an English version (either replace the Chinese text
or add a full English translation alongside the Chinese), ensuring all headings,
descriptions, table entries, code comments/examples and appended notes (e.g.,
"文档版本", "最后更新") are converted to proper English, and keep structural identifiers
(like class/interface names: CloudBus, DatabaseFacade, ThreadFacade, FlowChain,
GlobalConfig, etc.) unchanged so readers can locate code references.

Comment on lines +1 to +141
# ZStack 构建加速指南

## 为什么 `-T 1C` 提升有限?

- **依赖链长**:header → core → plugin* → premium,大量模块串行,可并行的只有同一“层”的模块。
- **单模块重**:mevoco、testlib-premium、hybrid、header 等单模块编译时间 30s~70s,并行只能缩短“层内”时间。
- **内存压力**:并行会起多个 JVM(GMavenPlus/编译器),若总内存不足易触发 GC 或交换,反而变慢。

---

## 推荐做法(按收益排序)

### 1. 日常开发:不 clean,只编改动的模块(收益最大)

**从 zstack 根目录构建 premium 时,必须先激活 profile `-P premium`**(根 pom 默认模块列表里没有 premium,只有激活该 profile 后 reactor 才会包含 premium)。

```bash
# 只编 premium 整棵子工程(含其依赖:header、core、plugin 等)
# -Dmevoco.skip.obfuscate=true 避免 mvnd 下 exec 插件 NPE,见下文说明
mvnd install -DskipTests -P premium -Dmevoco.skip.obfuscate=true -pl premium -am

# 只编 premium 下某模块(如 mevoco)及其依赖
mvnd install -DskipTests -P premium -Dmevoco.skip.obfuscate=true -pl premium/mevoco -am

# 只编 premium 下某插件
mvnd install -DskipTests -P premium -Dmevoco.skip.obfuscate=true -pl premium/plugin-premium/某插件名 -am
```

若**已进入 premium 目录**,则不需要 `-P premium`,直接:

```bash
cd premium && mvnd install -DskipTests -Dmevoco.skip.obfuscate=true

# 只编 premium 内某模块
cd premium && mvnd install -DskipTests -Dmevoco.skip.obfuscate=true -pl mevoco -am
```

避免每次 `clean` 全量重编,可节省大半时间。

### 2. 使用 Maven Daemon(mvnd)— 二次及以后构建明显更快

mvnd 保持 JVM 常驻,避免每次冷启动和重复加载类,对多模块项目通常有 **2~4 倍** 提升。

```bash
# macOS(mvnd 需通过官方 tap 安装,默认 brew 无此 formula)
brew install mvndaemon/homebrew-mvnd/mvnd

# 使用方式与 mvn 一致
mvnd clean install -DskipTests
mvnd install -DskipTests -pl premium/mevoco -am
```

**使用 mvnd 构建含 premium 时**:exec-maven-plugin 在 mvnd 下会因 `MavenSession.getContainer()` 为 null 报错,需跳过 mevoco 的混淆步骤:

```bash
mvnd install -DskipTests -P premium -Dmevoco.skip.obfuscate=true
```

需要发布/混淆时用普通 Maven:`mvn install -DskipTests -P premium`(不加该参数)。

注意:首次运行仍会较慢,后续构建才会体现加速。

**mvnd 内存与 Groovy 编译**:test-premium、test、testlib-premium 等模块大量使用 Groovy,gmavenplus 会在同一 JVM(mvnd daemon)里解析/编译,易占满堆或 Metaspace 导致无响应。已为 gmavenplus-plugin 开启 **`<fork>true</fork>`**:Groovy 编译在独立子进程中执行,完成后子进程退出释放内存,减轻 daemon 压力。根目录 `.mvn/jvm.config` 已为 mvnd daemon 配置:

- **堆**:`-Xmx10240m`(10GB),供 Groovy 源码与 stub 常驻
- **Metaspace**:`-XX:MaxMetaspaceSize=2560m`(2.5GB),应对大量 Groovy/Java 类加载
- **CodeCache**:`-XX:ReservedCodeCacheSize=512m`、`-XX:NonProfiledCodeHeapSize=240m`,避免 JIT 代码缓存满
- **G1**:`-XX:+UseG1GC`、`-XX:+UseStringDeduplication`

建议物理内存 ≥16GB 再跑 `mvnd install -DskipTests -P premium` 全量;否则可先用 `mvn` 或只编到 `test`(`-pl test -am`)。修改 `.mvn/jvm.config` 后必须重启 daemon 才生效:

```bash
mvnd --stop
mvnd install -DskipTests -P premium -Dmevoco.skip.obfuscate=true
```

### 3. 离线构建(依赖已齐全时)

```bash
mvn install -DskipTests -o
```

避免每次解析/检查远程仓库,可省数秒到数十秒。

### 4. 适当提高并行度(在内存充足时)

若机器内存 ≥16GB,可尝试:

```bash
# 最多 4 个线程,避免过多并行导致 OOM
mvn install -DskipTests -T 4
```

不建议在 8GB 或以下机器用 `-T 1C`,易与 `.mvn/jvm.config` 里的大堆一起导致交换。

### 5. 只编到某模块,不编完全部

```bash
# 只编到 build(含 header/core/plugin 等),不编 test/testlib/premium 测试相关
mvn install -DskipTests -pl build -am

# 只编到 crypto,不编 testlib-premium / test-premium
mvn install -DskipTests -pl premium/crypto -am
```

适合验证主链或某个子模块,跳过最耗时的 testlib-premium 等。

### 6. 关闭/缩短测试(已有 -DskipTests 时可略过)

已使用 `-DskipTests` 时,测试已跳过。若某处仍会跑测试,可再加:

```bash
-Dmaven.test.skip=true
```

---

## 建议的日常组合

| 场景 | 命令示例 |
|------|----------|
| 从**根目录**只编 premium | `mvnd install -DskipTests -P premium -Dmevoco.skip.obfuscate=true -pl premium -am` |
| 从**根目录**只编 mevoco | `mvnd install -DskipTests -P premium -Dmevoco.skip.obfuscate=true -pl premium/mevoco -am` |
| 已 **cd premium**,只编某插件 | `mvnd install -DskipTests -Dmevoco.skip.obfuscate=true -pl plugin-premium/xxx -am` |
| 全量构建(含 premium,少用 clean) | `mvnd install -DskipTests -P premium -Dmevoco.skip.obfuscate=true` |
| 全量 + 并行(内存够) | `mvnd install -DskipTests -P premium -Dmevoco.skip.obfuscate=true -T 4` |
| 依赖已全、不想连网 | `mvnd install -DskipTests -P premium -Dmevoco.skip.obfuscate=true -o` |

---

## 可选:为编译器开启增量(需各模块兼容)

在根 `pom.xml` 的 `maven-compiler-plugin` 的 `pluginManagement` 里可统一开启:

```xml
<configuration>
<useIncrementalCompilation>true</useIncrementalCompilation>
</configuration>
```

部分老模块若出现“改完不重编”的异常,再对单模块关闭即可。当前项目未全局开启,可按需试验。
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

请确认文档语言是否符合仓库规范

当前文档为中文,若仓库要求英文文档,请补充英文版本或改为英文。

As per coding guidelines: 代码里不应当有有中文,包括报错、注释等都应当使用正确的、无拼写错误的英文来写

🧰 Tools
🪛 LanguageTool

[uncategorized] ~94-~94: 您的意思是“易"于"”吗?
Context: ...ests -T 4 ``` 不建议在 8GB 或以下机器用 -T 1C,易与 `.mvn/jvm.config` 里的大堆一起导致交换。 ### 5. 只...

(YU7_YU8)

🪛 markdownlint-cli2 (0.20.0)

[warning] 121-121: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 121-121: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 121-121: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 121-121: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)

🤖 Prompt for AI Agents
In `@docs/BUILD-ACCELERATION.md` around lines 1 - 141, The document "ZStack
构建加速指南" is written in Chinese but the repo requires English-only documentation;
please convert this file to English (or add a complete English version alongside
the Chinese) so all headings (e.g., "ZStack 构建加速指南"), section titles, prose, and
any inline comments are in correct English and free of spelling errors; keep
command examples and flags unchanged, preserve technical details (mvnd/mvn
usage, profiles, flags like -P premium, -Dmevoco.skip.obfuscate=true,
.mvn/jvm.config settings, and plugin notes such as gmavenplus fork) and, if you
retain the Chinese version, add a clear bilingual header indicating both
languages and ensure filenames/links referenced in the text remain consistent.

Comment on lines +54 to 57
job = (LongJob) it.getConstructor().newInstance();
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

反射调用更新正确,但异常处理应使用日志而非 printStackTrace()

使用 e.printStackTrace() 将异常输出到标准错误流不是最佳实践,应该使用已有的 logger 进行记录。

🛠️ 建议的修复方案
             try {
                 job = (LongJob) it.getConstructor().newInstance();
             } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
-                e.printStackTrace();
+                logger.warn(String.format("failed to instantiate LongJob class[%s]", it.getName()), e);
             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
job = (LongJob) it.getConstructor().newInstance();
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
job = (LongJob) it.getConstructor().newInstance();
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
logger.warn(String.format("failed to instantiate LongJob class[%s]", it.getName()), e);
}
🤖 Prompt for AI Agents
In `@longjob/src/main/java/org/zstack/longjob/LongJobFactoryImpl.java` around
lines 54 - 57, The catch block in LongJobFactoryImpl that handles
InstantiationException | IllegalAccessException | NoSuchMethodException |
InvocationTargetException currently calls e.printStackTrace(); replace this with
a proper logger call (e.g., logger.error("Failed to instantiate LongJob via
reflection for " + it.getName(), e)) so the exception is recorded via the class
logger; update the catch to log the exception and any contextual info (the
constructor/type or variable job) instead of printing the stack trace.

AlanJager and others added 25 commits February 12, 2026 12:02
…isk offering

Resolves: ZSTAC-74683

Change-Id: Id0339ed0221e92e506f60745cde972cc3ee6d9ae
When anti-split-brain check selects a disconnected MDS node, the HTTP
call now times out after 30s instead of 5+ minutes, and automatically
retries the next available MDS via tryNext mechanism.

Resolves: ZSTAC-80595

Change-Id: I1be80f1b70cad1606eb38d1f0078c8f2781e6941
When MN restarts during a destroy operation, the hypervisor may report
the VM as Stopped. Without this transition, the state machine throws
an exception and the VM stays stuck in Destroying state forever.

Resolves: ZSTAC-80620

Change-Id: I037edba70d145a44a88ce0d3573089182fedb162
…pacity

Resolves: ZSTAC-79709

Change-Id: I45a2133bbb8c51c25ae3549d59e588976192a08d
Resolves: ZSTAC-78989

Change-Id: I0fe3a56ab724978944c69afadaab7ff7353e4c0f
Resolves: ZSTAC-82153

Change-Id: Ib51c2e21553277416d1a9444be55aca2aa4b2fc4
…fterAddIpAddress to prevent NPE during rollback

Resolves: ZSTAC-81741

Change-Id: I53bcf20a10306afc7b6172da294d347b74e6c41f
Resolves: ZSTAC-81182

Change-Id: Id1bb642154dc66ae9995dcc4d9fc00cdce9bcaf8
<fix>[vm]: add Destroying->Stopped state transition

See merge request zstackio/zstack!9156
<fix>[i18n]: improve snapshot error message for unattached volume

See merge request zstackio/zstack!9192
<fix>[zbs]: reduce mds connect timeout and enable tryNext for volume clients

See merge request zstackio/zstack!9153
<fix>[vm]: use max of virtual and actual size for root disk allocation

See merge request zstackio/zstack!9155
…talling

In dual management node scenarios, concurrent modifications to the
consistent hash ring from heartbeat reconciliation and canonical event
callbacks can cause NodeHash/Nodes inconsistency, leading to message
routing failures and task timeouts.

Fix: (1) synchronized all ResourceDestinationMakerImpl methods to
ensure atomic nodeHash+nodes updates, (2) added lifecycleLock in
ManagementNodeManagerImpl to serialize heartbeat reconciliation with
event callbacks, (3) added two-round delayed confirmation before
removing nodes from hash ring to avoid race with NodeJoin events.

Resolves: ZSTAC-77711

Change-Id: I3d33d53595dd302784dff17417a5b25f2d0f3426
<fix>[network]: filter reserved IPs in getFreeIp

See merge request zstackio/zstack!9170
<fix>[ceph]: apply over-provisioning ratio when releasing snapshot capacity

See merge request zstackio/zstack!9162
<fix>[network]: add null check for L3 network system tags in API interceptor

See merge request zstackio/zstack!9169
The mdsUrls field in ExternalPrimaryStorage config contains
user:password@host format credentials. Add desensitization to
mask credentials as ***@host in API/CLI output.

Resolves: ZSTAC-80664

Change-Id: I94bdede5a1b52eb039de70efb5458693484405f7
Add ORG_ZSTACK_STORAGE_BACKUP_CANCEL_TIMEOUT constant to
CloudOperationsErrorCode for use in premium volumebackup module.

Resolves: ZSTAC-82195

Change-Id: Ibc405876e1171b637cf76b91a6822574fb6e7811
<fix>[core]: synchronize consistent hash ring to prevent dual-MN race condition

See merge request zstackio/zstack!9154
SyncTaskFuture constructor calls Context.current() unconditionally,
triggering ServiceLoader for ContextStorageProvider even when
telemetry is disabled. If sentry-opentelemetry-bootstrap jar is
on classpath, ServiceLoader fails with "not a subtype" due to
ClassLoader isolation in Tomcat, throwing ServiceConfigurationError
(extends Error) that escapes all catch(Exception) blocks.

1. Why is this change necessary?
MN startup crashes with ORG_ZSTACK_CORE_WORKFLOW_10001 because
Context.current() triggers ServiceLoader unconditionally in
SyncTaskFuture constructor, even when telemetry is disabled.

2. How does it address the problem?
Only call Context.current() when isTelemetryEnabled() returns
true, matching the existing guard pattern used in other
DispatchQueueImpl code paths (lines 351, 1069).

3. Are there any side effects?
None. When telemetry is disabled, parentContext was never used.

# Summary of changes (by module):
- core/thread: conditionalize Context.current() in SyncTaskFuture

Related: ZSTAC-82275
Change-Id: I5c0e1f15769c746c630028a29df8cf1815620608
<fix>[thread]: guard Context.current() with telemetry check

See merge request zstackio/zstack!9202
<fix>[loadBalancer]: block SLB deletion during grayscale upgrade

See merge request zstackio/zstack!9187
<fix>[volumebackup]: add backup cancel timeout error code

See merge request zstackio/zstack!9200
<fix>[core]: add @nologging to sensitive config fields

See merge request zstackio/zstack!9171
ZQLImpact

Change-Id: I656f6963726b77727363656d756465766b6a7a6a
@MatheMatrix MatheMatrix force-pushed the sync/ye.zou/java17@@2 branch from 0946f28 to 8d17abe Compare February 17, 2026 11:38
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
core/src/main/java/org/zstack/core/cloudbus/ResourceDestinationMakerImpl.java (1)

80-92: ⚠️ Potential issue | 🔴 Critical

getNodeInfo 方法会返回 null,导致调用方产生 NullPointerException。

第 89 行使用 nodes.put() 的返回值赋给 info,但 Map.put() 返回的是该键的旧值。在首次加载节点时,由于键在 map 中不存在,put() 返回 null,导致 info 被赋为 null,最终方法返回 null

调用方(如 ManagementNodeManagerImpl.java:893 和 CloudBusImpl3.java:657)直接调用返回值的方法(.getNodeInfo(nodeUuid).getNodeIP()),未进行空值检查,会触发 NullPointerException。

🛠️ 建议修复
             nodeHash.add(nodeUuid);
-            info = nodes.put(nodeUuid, new NodeInfo(vo));
+            info = new NodeInfo(vo);
+            nodes.put(nodeUuid, info);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@core/src/main/java/org/zstack/core/cloudbus/ResourceDestinationMakerImpl.java`
around lines 80 - 92, getNodeInfo currently assigns info = nodes.put(nodeUuid,
new NodeInfo(vo)) which returns the previous value and yields null on first
insert; change the logic in getNodeInfo to create the new NodeInfo and store it,
then assign and return the newly created instance (e.g., NodeInfo created = new
NodeInfo(vo); nodes.put(nodeUuid, created); info = created;). Ensure you still
add nodeUuid to nodeHash and preserve the dbf.findByUuid(…) and
ManagementNodeNotFoundException behavior so callers of getNodeInfo (e.g.,
ManagementNodeManagerImpl and CloudBusImpl3) receive a non-null NodeInfo.
compute/src/main/java/org/zstack/compute/vm/VmNicManagerImpl.java (1)

56-64: ⚠️ Potential issue | 🟠 Major

数据一致性问题:在检查 NIC 是否存在之前已更新 UsedIpVO。

第 58 行在检查 VmNicVO 是否存在(第 61 行)之前,已经将 UsedIpVO.vmNicUuid 更新为传入的 vmNicUUid。如果 NIC 不存在,这将导致 UsedIpVO 记录指向一个不存在的 NIC UUID。

建议将 null 检查移到 SQL 更新之前:

🛠️ 建议的修复方案
 `@Override`
 public void afterAddIpAddress(String vmNicUUid, String usedIpUuid) {
-    /* update UsedIpVO */
-    SQL.New(UsedIpVO.class).eq(UsedIpVO_.uuid, usedIpUuid).set(UsedIpVO_.vmNicUuid, vmNicUUid).update();
-
     VmNicVO nic = Q.New(VmNicVO.class).eq(VmNicVO_.uuid, vmNicUUid).find();
     if (nic == null) {
         logger.debug(String.format("VmNic[uuid:%s] not found, skip afterAddIpAddress", vmNicUUid));
         return;
     }
+
+    /* update UsedIpVO */
+    SQL.New(UsedIpVO.class).eq(UsedIpVO_.uuid, usedIpUuid).set(UsedIpVO_.vmNicUuid, vmNicUUid).update();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@compute/src/main/java/org/zstack/compute/vm/VmNicManagerImpl.java` around
lines 56 - 64, In afterAddIpAddress, the code updates UsedIpVO
(SQL.New(UsedIpVO.class)...set(UsedIpVO_.vmNicUuid, vmNicUUid).update()) before
verifying the NIC exists (Q.New(VmNicVO.class).eq(VmNicVO_.uuid,
vmNicUUid).find()); move the null-check and early return for VmNicVO 'nic' to
before the SQL update so you only set UsedIpVO.vmNicUuid when the VmNicVO is
present, keeping the logger.debug(String.format(...)) and return behavior
intact.
🧹 Nitpick comments (7)
storage/src/main/java/org/zstack/storage/primary/AbstractUsageReport.java (2)

447-450: 与第 358 行相同的问题:捕获 Exception 过于宽泛。

建议同步修改为 ReflectiveOperationException,保持代码一致性。

♻️ 建议的修改
 try {
     vo = usageClass.getConstructor().newInstance();
-} catch (Exception e) {
+} catch (ReflectiveOperationException e) {
     throw new RuntimeException(e);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@storage/src/main/java/org/zstack/storage/primary/AbstractUsageReport.java`
around lines 447 - 450, Replace the overly broad catch of Exception in
AbstractUsageReport around the reflective instantiation (the block with
usageClass.getConstructor().newInstance()) with a catch for
ReflectiveOperationException to match the handling at line 358; update the catch
clause to "catch (ReflectiveOperationException e)" and rethrow as
RuntimeException(e) (and add any necessary import) so reflection-specific errors
are handled consistently.

357-360: 捕获 Exception 过于宽泛,建议使用更精确的异常类型。

getConstructor().newInstance() 会抛出 NoSuchMethodExceptionInstantiationExceptionIllegalAccessExceptionInvocationTargetException。捕获通用的 Exception 可能会掩盖其他编程错误。建议使用 ReflectiveOperationException(这些反射异常的公共父类)或显式列出具体异常类型。

此问题同样存在于第 448 行。

♻️ 建议的修改
 try {
     vo = usageClass.getConstructor().newInstance();
-} catch (Exception e) {
+} catch (ReflectiveOperationException e) {
     throw new RuntimeException(e);
 }

As per coding guidelines: "捕获异常时需严格匹配期望的异常类型或其父类,避免捕获错误的异常类型"。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@storage/src/main/java/org/zstack/storage/primary/AbstractUsageReport.java`
around lines 357 - 360, The code currently catches a broad Exception around
usageClass.getConstructor().newInstance() in AbstractUsageReport which is too
generic; replace the catch (Exception e) with either catch
(ReflectiveOperationException e) or explicit catches for NoSuchMethodException,
InstantiationException, IllegalAccessException, and InvocationTargetException to
precisely handle reflection failures, and apply the same change to the other
occurrence (around line 448) in the class; keep existing error handling/throwing
behavior but narrow the caught exception type(s).
plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsStorageController.java (1)

182-185: 建议将超时时间提取为常量。

根据编码规范,应避免使用魔法值。这里的 30 秒超时值可以提取为类常量,以提高可读性和可维护性。

♻️ 建议的修改

在类常量区域添加:

private static final long GET_VOLUME_CLIENTS_TIMEOUT_SECONDS = 30;

然后修改调用处:

-            GetVolumeClientsRsp rsp = new HttpCaller<>(GET_VOLUME_CLIENTS_PATH, cmd, GetVolumeClientsRsp.class,
-                    null, TimeUnit.SECONDS, 30, true)
+            GetVolumeClientsRsp rsp = new HttpCaller<>(GET_VOLUME_CLIENTS_PATH, cmd, GetVolumeClientsRsp.class,
+                    null, TimeUnit.SECONDS, GET_VOLUME_CLIENTS_TIMEOUT_SECONDS, true)
                     .setTryNext(true)
                     .syncCall();

根据编码规范:"避免使用魔法值(Magic Value):直接使用未经定义的数值或字符串应替换为枚举或常量"。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsStorageController.java`
around lines 182 - 185, Extract the hard-coded timeout 30 into a class constant
in ZbsStorageController (e.g., add private static final long
GET_VOLUME_CLIENTS_TIMEOUT_SECONDS = 30) and replace the literal in the
HttpCaller instantiation for GET_VOLUME_CLIENTS_PATH (the call that returns
GetVolumeClientsRsp) with that constant; ensure the constant name clearly
reflects seconds and update the HttpCaller parameter list to use
GET_VOLUME_CLIENTS_TIMEOUT_SECONDS to remove the magic value.
plugin/sugonSdnController/src/main/java/org/zstack/sugonSdnController/controller/api/ApiConnectorImpl.java (1)

166-168: 考虑移除已弃用的 finalize() 方法。

finalize() 自 Java 9 起已被弃用,并将在 Java 18+ 中标记为"弃用待移除"。虽然在 Java 17 中仍可工作,但建议在后续迭代中将资源清理逻辑迁移到显式的 dispose() 方法或实现 AutoCloseable 接口配合 try-with-resources 使用。

当前类已有 dispose() 方法(第 703-711 行),可直接移除 finalize() 并确保调用方在使用完毕后显式调用 dispose()

♻️ 建议移除 finalize() 方法
-    `@Override`
-    protected void finalize() {
-        dispose();
-    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugin/sugonSdnController/src/main/java/org/zstack/sugonSdnController/controller/api/ApiConnectorImpl.java`
around lines 166 - 168, Remove the deprecated finalize() override from
ApiConnectorImpl and rely on the existing dispose() method for cleanup; delete
the protected void finalize() { dispose(); } method, and either update callers
to invoke ApiConnectorImpl.dispose() explicitly or make ApiConnectorImpl
implement AutoCloseable and move/rename dispose() to close() so it can be used
with try-with-resources (ensure dispose()/close() behavior remains unchanged).
plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerApiInterceptor.java (1)

162-170: 建议对 UUID 参数进行 trim 处理,并使用错误码常量

根据编码规范和已有学习记录,Interceptor 中来自 Message 的字符串参数(包括 UUID)应当进行 trim 处理,以防止用户复制粘贴时带入的空格或换行符影响逻辑。

此外,为保持与本文件其他 validate 方法的一致性,建议使用错误码常量(如 ORG_ZSTACK_NETWORK_SERVICE_LB_XXXXX)而非直接硬编码错误消息。

♻️ 建议修改
 private void validate(APIDeleteLoadBalancerMsg msg) {
+    if (msg.getUuid() != null) {
+        msg.setUuid(msg.getUuid().trim());
+    }
     if (UpgradeGlobalConfig.GRAYSCALE_UPGRADE.value(Boolean.class)) {
         LoadBalancerVO lb = dbf.findByUuid(msg.getUuid(), LoadBalancerVO.class);
         if (lb != null && lb.getType() == LoadBalancerType.SLB) {
             throw new ApiMessageInterceptionException(argerr(
-                    "cannot delete the standalone load balancer[uuid:%s] during grayscale upgrade", msg.getUuid()));
+                    ORG_ZSTACK_NETWORK_SERVICE_LB_XXXXX, "cannot delete the standalone load balancer[uuid:%s] during grayscale upgrade", msg.getUuid()));
         }
     }
 }

Based on learnings: SecurityGroupApiInterceptor.java 中需要对以下类型的外部字符串参数进行 trim 处理:... UUID字段... 这些参数在进入 validate 方法之前都应当被 trim,以防止空格、换行符等不可见字符影响验证逻辑。 As per coding guidelines: **/*Interceptor.java: 注意检查来自 Message 的参数是否做过 trim

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerApiInterceptor.java`
around lines 162 - 170, In
LoadBalancerApiInterceptor.validate(APIDeleteLoadBalancerMsg msg) trim the
incoming UUID before use (e.g., use a local String uuid = msg.getUuid() == null
? null : msg.getUuid().trim()) and pass that trimmed uuid to dbf.findByUuid;
when throwing the exception replace the hardcoded message with the project error
code constant (e.g., ORG_ZSTACK_NETWORK_SERVICE_LB_XXXXX) so the
ApiMessageInterceptionException/argerr call uses the constant and the trimmed
uuid for formatting, preserving existing null-safety and behavior.
build/fix-groovy-compiler.sh (1)

26-66: STANDARD_JAVA_CONFIG / GMAVENPLUS_CONFIG 未被使用

这两个变量当前未被引用,建议删除或在替换逻辑中实际使用,避免误导读者。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@build/fix-groovy-compiler.sh` around lines 26 - 66, The two shell variables
STANDARD_JAVA_CONFIG and GMAVENPLUS_CONFIG are defined but never used; either
remove these unused definitions or wire them into the template/replacement logic
that builds module pom fragments (e.g., where plugin config is injected) so the
correct XML is inserted for modules with/without Groovy; specifically either
delete STANDARD_JAVA_CONFIG and GMAVENPLUS_CONFIG, or update the script’s
replacement/templating function (the code that assembles/chooses plugin XML) to
reference these symbols when selecting the Java-only vs GMavenPlus plugin
blocks.
portal/src/main/java/org/zstack/portal/managementnode/ManagementNodeManagerImpl.java (1)

877-925: 建议统一生命周期事件的锁入口以降低竞态风险

当前只在回调与哈希环对账路径使用 lifecycleLock;心跳路径(如 nodeDie()/fenceSuspects 触发的 nodeLeft)仍可能与回调线程并发,导致 join/left 顺序交错。建议将这些入口也统一经由同一把锁分发,以彻底串行化生命周期事件。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@portal/src/main/java/org/zstack/portal/managementnode/ManagementNodeManagerImpl.java`
around lines 877 - 925, The lifecycle events (nodeJoin/nodeLeft) are only
synchronized in the hash-ring reconciliation block using lifecycleLock, leaving
other paths like nodeDie(), fenceSuspects(), and direct calls to
nodeLifeCycle.nodeLeft(...) susceptible to race conditions; change those entry
points to acquire the same lifecycleLock (or route all lifecycle dispatch
through a single synchronized helper method) so join/left handling is fully
serialized — update nodeDie(), fenceSuspects(), any callbacks that call
nodeLifeCycle.nodeLeft(...) and the existing dispatch to call that synchronized
helper (or wrap their bodies with synchronized(lifecycleLock)) to ensure a
single lock protects all lifecycle transitions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java`:
- Around line 1078-1083: The removal logic in L3BasicNetwork that iterates
freeIpInventorys against self.getReservedIpRanges() must first filter reserved
ranges by the IP version to avoid IPv4/IPv6 cross-comparisons; modify the code
around freeIpInventorys.removeIf(...) to compute the ipVersion of each freeIp
(use freeIp.getIpVersion() or derive it consistently with the
handle(ReturnIpMsg) pattern) and only match against reserved ranges filtered via
getReservedIpRanges().stream().filter(r -> r.getIpVersion() ==
ipVersion).collect(...), then call NetworkUtils.isInRange(...) against that
filtered list when deciding to remove the free IP.

---

Outside diff comments:
In `@compute/src/main/java/org/zstack/compute/vm/VmNicManagerImpl.java`:
- Around line 56-64: In afterAddIpAddress, the code updates UsedIpVO
(SQL.New(UsedIpVO.class)...set(UsedIpVO_.vmNicUuid, vmNicUUid).update()) before
verifying the NIC exists (Q.New(VmNicVO.class).eq(VmNicVO_.uuid,
vmNicUUid).find()); move the null-check and early return for VmNicVO 'nic' to
before the SQL update so you only set UsedIpVO.vmNicUuid when the VmNicVO is
present, keeping the logger.debug(String.format(...)) and return behavior
intact.

In
`@core/src/main/java/org/zstack/core/cloudbus/ResourceDestinationMakerImpl.java`:
- Around line 80-92: getNodeInfo currently assigns info = nodes.put(nodeUuid,
new NodeInfo(vo)) which returns the previous value and yields null on first
insert; change the logic in getNodeInfo to create the new NodeInfo and store it,
then assign and return the newly created instance (e.g., NodeInfo created = new
NodeInfo(vo); nodes.put(nodeUuid, created); info = created;). Ensure you still
add nodeUuid to nodeHash and preserve the dbf.findByUuid(…) and
ManagementNodeNotFoundException behavior so callers of getNodeInfo (e.g.,
ManagementNodeManagerImpl and CloudBusImpl3) receive a non-null NodeInfo.

---

Duplicate comments:
In `@build/debug.sh`:
- Around line 11-16: Replace the Chinese comment "# 加载Java 17 JVM参数" with a
clear English comment (e.g. "# Load Java 17 JVM options") in build/debug.sh near
the jvm_options_file variable; ensure the new comment is grammatically correct
and follows repository style for comments and error/messages (check related
nearby comments for consistency), and keep the same placement so
jvm_options_file, jvm_options, and the following if-block remain unchanged.

In `@build/fix-groovy-compiler.sh`:
- Around line 95-266: The heredoc for the embedded Python uses unquoted
delimiter `python3 <<PYTHON_SCRIPT` which allows shell expansion (breaking
occurrences like ${project.java.version}); change the heredoc to a single-quoted
delimiter `<<'PYTHON_SCRIPT'` and ensure the Python script receives the target
path via an explicit environment/argument (the `FILE` variable) instead of
relying on shell interpolation; update the invocation around the existing
`python3 <<PYTHON_SCRIPT` and any references to `FILE` so the script reads the
filename from an exported env var or a positional argument passed to python to
prevent shell expansion of XML placeholders.
- Around line 3-16: Update the Groovy fix script (build/fix-groovy-compiler.sh)
to replace all Chinese text with proper English: change the top comment block
and inline comment describing the script purpose, and replace the echo output
strings ("批量修复Groovy编译配置" and any surrounding lines) with concise English
equivalents; ensure variables and behavior (SCRIPT_DIR, PROJECT_ROOT, set -e, cd
"$PROJECT_ROOT") remain unchanged and preserve spelling/grammar in the new
English comments and messages.

In `@build/zstack`:
- Around line 23-28: The inline comment above the Java 17 JVM options block is
in Chinese; replace it with an English comment to match repository conventions.
Update the comment that currently precedes the jvmOptionsFile declaration to an
English phrase such as "Load Java 17 JVM options" (or similar), and ensure
surrounding identifiers jvmOptionsFile, jvmOptions and confDir remain unchanged.

In `@build/zstack-debug`:
- Around line 29-30: The JVM options variable name is misspelled: it's defined
as javaOptitons but invoked as javaOptions, so the JVM debug/memory flags are
dropped; fix by making the variable name consistent (e.g., rename the definition
javaOptitons to javaOptions or change the invocation to use javaOptitons) so the
java invocation that runs org.zstack.portal.main.Main uses the intended JVM
arguments.
- Around line 23-28: Replace the Chinese comment above the jvmOptionsFile
declaration with a clear English comment (e.g., change "# 加载Java 17 JVM参数" to
"Load Java 17 JVM options") so that the comment for the jvmOptionsFile and
jvmOptions variables is in proper English and follows repository guidelines.

In `@conf/jvm-options-java17.conf`:
- Around line 1-18: The top-of-file comments in the JVM options block are
written in Chinese and must be rewritten in English to follow repository
guidelines; update the comment lines that describe "Java 17 模块系统配置",
"开放反射访问(226处setAccessible需要)", "导出内部API(sun.misc.Signal需要)", "Hibernate需要", and
"AspectJ需要" to clear, correct English descriptions (e.g., "Java 17 module system
configuration", "Allow reflective access (needed for setAccessible calls)",
"Export internal APIs (needed for sun.misc.Signal)", "Required by Hibernate",
"Required by AspectJ") while keeping the JVM flags (--add-opens and
--add-exports entries such as --add-opens java.base/java.lang=ALL-UNNAMED,
--add-exports java.base/sun.misc=ALL-UNNAMED, etc.) unchanged.

In `@docs/ARCHITECTURE.md`:
- Around line 1-1199: The document "ZStack 后端架构索引" is written in Chinese which
violates the codebase language policy; replace all Chinese content with a clear,
idiomatic English translation while preserving technical identifiers (e.g.,
CloudBus, CloudBusImpl3, Platform, DatabaseFacade, ThreadFacade, ErrorFacade,
APICreateVmInstanceMsg, VmInstanceVO, GlobalConfig, FlowChain, SimpleFlowChain,
PluginDSL, RateLimiter, etc.), code blocks, annotations (`@RestRequest`,
`@APIParam`, `@EO`) and diagrams exactly as-is; ensure headings, captions, table
text, comments and prose are fully translated to English (no Chinese characters
or transliterations remain), keep original section structure and examples
intact, and update the document metadata (title, language note, "最后更新") to
English equivalents.

In `@docs/BUILD-ACCELERATION.md`:
- Around line 1-141: The document docs/BUILD-ACCELERATION.md is written in
Chinese and must be converted or supplemented with an English version to meet
repository language guidelines; replace or append an English translation for the
title "# ZStack 构建加速指南" and all section headings and body text (including code
block descriptions and notes about mvnd, JVM settings, and examples) so every
user-facing string is available in clear, correct English while preserving the
original command examples and configuration values.

In `@longjob/src/main/java/org/zstack/longjob/LongJobFactoryImpl.java`:
- Around line 53-57: In LongJobFactoryImpl replace the e.printStackTrace() in
the catch block around job = (LongJob) it.getConstructor().newInstance(); with a
logger.error call that logs a clear message and the exception (e) — e.g.
logger.error("Failed to instantiate LongJob for class " + it.getName(), e); — so
the InstantiationException, IllegalAccessException, NoSuchMethodException and
InvocationTargetException are recorded via the existing logger instead of
printing stacktrace to stdout.

---

Nitpick comments:
In `@build/fix-groovy-compiler.sh`:
- Around line 26-66: The two shell variables STANDARD_JAVA_CONFIG and
GMAVENPLUS_CONFIG are defined but never used; either remove these unused
definitions or wire them into the template/replacement logic that builds module
pom fragments (e.g., where plugin config is injected) so the correct XML is
inserted for modules with/without Groovy; specifically either delete
STANDARD_JAVA_CONFIG and GMAVENPLUS_CONFIG, or update the script’s
replacement/templating function (the code that assembles/chooses plugin XML) to
reference these symbols when selecting the Java-only vs GMavenPlus plugin
blocks.

In
`@plugin/loadBalancer/src/main/java/org/zstack/network/service/lb/LoadBalancerApiInterceptor.java`:
- Around line 162-170: In
LoadBalancerApiInterceptor.validate(APIDeleteLoadBalancerMsg msg) trim the
incoming UUID before use (e.g., use a local String uuid = msg.getUuid() == null
? null : msg.getUuid().trim()) and pass that trimmed uuid to dbf.findByUuid;
when throwing the exception replace the hardcoded message with the project error
code constant (e.g., ORG_ZSTACK_NETWORK_SERVICE_LB_XXXXX) so the
ApiMessageInterceptionException/argerr call uses the constant and the trimmed
uuid for formatting, preserving existing null-safety and behavior.

In
`@plugin/sugonSdnController/src/main/java/org/zstack/sugonSdnController/controller/api/ApiConnectorImpl.java`:
- Around line 166-168: Remove the deprecated finalize() override from
ApiConnectorImpl and rely on the existing dispose() method for cleanup; delete
the protected void finalize() { dispose(); } method, and either update callers
to invoke ApiConnectorImpl.dispose() explicitly or make ApiConnectorImpl
implement AutoCloseable and move/rename dispose() to close() so it can be used
with try-with-resources (ensure dispose()/close() behavior remains unchanged).

In `@plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsStorageController.java`:
- Around line 182-185: Extract the hard-coded timeout 30 into a class constant
in ZbsStorageController (e.g., add private static final long
GET_VOLUME_CLIENTS_TIMEOUT_SECONDS = 30) and replace the literal in the
HttpCaller instantiation for GET_VOLUME_CLIENTS_PATH (the call that returns
GetVolumeClientsRsp) with that constant; ensure the constant name clearly
reflects seconds and update the HttpCaller parameter list to use
GET_VOLUME_CLIENTS_TIMEOUT_SECONDS to remove the magic value.

In
`@portal/src/main/java/org/zstack/portal/managementnode/ManagementNodeManagerImpl.java`:
- Around line 877-925: The lifecycle events (nodeJoin/nodeLeft) are only
synchronized in the hash-ring reconciliation block using lifecycleLock, leaving
other paths like nodeDie(), fenceSuspects(), and direct calls to
nodeLifeCycle.nodeLeft(...) susceptible to race conditions; change those entry
points to acquire the same lifecycleLock (or route all lifecycle dispatch
through a single synchronized helper method) so join/left handling is fully
serialized — update nodeDie(), fenceSuspects(), any callbacks that call
nodeLifeCycle.nodeLeft(...) and the existing dispatch to call that synchronized
helper (or wrap their bodies with synchronized(lifecycleLock)) to ensure a
single lock protects all lifecycle transitions.

In `@storage/src/main/java/org/zstack/storage/primary/AbstractUsageReport.java`:
- Around line 447-450: Replace the overly broad catch of Exception in
AbstractUsageReport around the reflective instantiation (the block with
usageClass.getConstructor().newInstance()) with a catch for
ReflectiveOperationException to match the handling at line 358; update the catch
clause to "catch (ReflectiveOperationException e)" and rethrow as
RuntimeException(e) (and add any necessary import) so reflection-specific errors
are handled consistently.
- Around line 357-360: The code currently catches a broad Exception around
usageClass.getConstructor().newInstance() in AbstractUsageReport which is too
generic; replace the catch (Exception e) with either catch
(ReflectiveOperationException e) or explicit catches for NoSuchMethodException,
InstantiationException, IllegalAccessException, and InvocationTargetException to
precisely handle reflection failures, and apply the same change to the other
occurrence (around line 448) in the class; keep existing error handling/throwing
behavior but narrow the caught exception type(s).

Comment on lines +1078 to +1083

Set<ReservedIpRangeVO> reservedIpRanges = self.getReservedIpRanges();
if (reservedIpRanges != null && !reservedIpRanges.isEmpty()) {
freeIpInventorys.removeIf(freeIp -> reservedIpRanges.stream().anyMatch(
r -> NetworkUtils.isInRange(freeIp.getIp(), r.getStartIp(), r.getEndIp())));
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

缺少 IP 版本过滤,可能导致 IPv4/IPv6 地址比较错误。

当前代码直接对所有 reservedIpRanges 进行遍历匹配,但 freeIpInventorys 可能同时包含 IPv4 和 IPv6 地址(见 lines 1025-1032),reservedIpRanges 也可能同时包含两种版本的保留范围。NetworkUtils.isInRange 会根据 IP 类型选择不同的比较逻辑,跨版本比较可能产生不正确的结果。

参考同文件 handle(ReturnIpMsg) 中的现有模式(lines 486-487),应先按 IP 版本过滤保留范围再进行比较:

List<ReservedIpRangeVO> ranges = self.getReservedIpRanges()
        .stream().filter(r -> r.getIpVersion() == ipVersion).collect(Collectors.toList());
建议的修复方案
         Set<ReservedIpRangeVO> reservedIpRanges = self.getReservedIpRanges();
         if (reservedIpRanges != null && !reservedIpRanges.isEmpty()) {
-            freeIpInventorys.removeIf(freeIp -> reservedIpRanges.stream().anyMatch(
-                    r -> NetworkUtils.isInRange(freeIp.getIp(), r.getStartIp(), r.getEndIp())));
+            freeIpInventorys.removeIf(freeIp -> reservedIpRanges.stream()
+                    .filter(r -> r.getIpVersion() == freeIp.getIpVersion())
+                    .anyMatch(r -> NetworkUtils.isInRange(freeIp.getIp(), r.getStartIp(), r.getEndIp())));
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@network/src/main/java/org/zstack/network/l3/L3BasicNetwork.java` around lines
1078 - 1083, The removal logic in L3BasicNetwork that iterates freeIpInventorys
against self.getReservedIpRanges() must first filter reserved ranges by the IP
version to avoid IPv4/IPv6 cross-comparisons; modify the code around
freeIpInventorys.removeIf(...) to compute the ipVersion of each freeIp (use
freeIp.getIpVersion() or derive it consistently with the handle(ReturnIpMsg)
pattern) and only match against reserved ranges filtered via
getReservedIpRanges().stream().filter(r -> r.getIpVersion() ==
ipVersion).collect(...), then call NetworkUtils.isInRange(...) against that
filtered list when deciding to remove the free IP.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants